From f0e12c851971a21f7aadf0835b016bfaf2067314 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:34:02 -0500 Subject: [PATCH 01/37] Add managed Slack setup with an independent own-app path (#15679) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Slack connects a person to an agent through an installed app and a verified identity. > - Independent installations must remain usable without Paperclip Cloud or manager credentials. > - Approved Cloud deployments can use Slack manager grants to create and install each agent's app. > - Both paths should share credential storage, identity checks, recovery, and the existing runtime. > - This pull request adds gated managed setup and keeps independent app setup under Advanced. > - It also makes ongoing connection settings and personal test messages easier to use. ## Linked Issues or Issue Description **Subsystem affected** Slack setup, connection settings, tenant credential storage, and the Cloud connector handoff. **Problem or motivation** Approved deployments need an installation flow without pasted configuration tokens. Open-source users must retain their independent Slack app flow. A connection manager also needs a simple way to check a bot from Settings. **Proposed solution** Use the managed path when the instance is configured to support it. Choose agent and Connect are the two visible stops. Continue starts authorization or installation. Use your own app remains under Advanced before creation. Save personal manager grants by company, Paperclip user, Slack user, workspace, and manager app. Reuse only verified identities for test messages. Unknown identities must complete a private Slack connection challenge before linking and receiving the test DM. **Alternatives considered** A shared bot would change each agent's Slack identity. Requiring Slack CLI or a hosted service for self-hosted users would remove independent installation. **Roadmap alignment** This maintainer-requested change improves Connected Apps and experimental Agent Chat. It adds provisioning and management controls to the existing Slack runtime. **Additional context** The companion Cloud broker profile is a separate deployment prerequisite. Managed setup remains gated. Existing-app adoption is deferred. ## What Changed - Extract Slack registration, provider requests, completion, customer-owned setup, managed setup, and personal manager grants into focused services. Preserve the compatibility facade and independent removal cleanup. - Add company-scoped manager grants and a registration reference through an additive, idempotent migration after current master. Keep runtime credentials separate from app-registration secrets. - Add authenticated manager authorization, sealed handoff, serialized refresh and revocation, app creation, managed installation, and installer-bound OAuth fallback. - Preserve creation intent, block automatic uncertain recreation, save credentials before downstream checks, and validate app, workspace, bot, scopes, and installer identity. - Add the two-stop production wizard and ordered stories. Keep the own-app, manual, existing-app, and legacy-draft paths. - Simplify Settings. Open Slack configuration targets the saved app. The test-message action has its own row. Remove the old manual-avatar section, channel-message section, and broken permissions link. - Add paginated account discovery and a private connection challenge for unknown accounts. Expired invitations stop waiting and offer a new private invitation. Save send receipts before provider dispatch and preserve confirmed delivery when activity logging fails. - Reinstall the saved managed app on explicit reconnect, replace revoked credentials, and resume completion. Persist reconnect intent and duplicate-request receipts without creating another app. - Update provider browser tests, recovery tests, secret redaction, setup documentation, and the agent prompt. Preserve current master's durable verification scheduling. ## Verification - Full workspace typecheck and repository build passed on `56d389d47a`. The subsequent reconnect retry fix in `288364eada` also passed UI typecheck and UI build. - 114 focused Slack registration cases and 13 production managed-wizard cases passed, including fresh-token reconnect, retry after installation failure, and duplicate reconnect requests. - All nine affected Slack browser cases passed against the production UI and a disposable fixture server. They cover managed setup, automatic setup, own-app selection, refresh, recovery, mobile behavior, and connection settings. - Eight focused real-database test-message cases passed. They cover a private ownership challenge, saved grant reuse, account pagination, revoked and conflicting links, workspace mismatch, disabled DMs, revoked membership, audit failure, ambiguous dispatch, and secret canaries. - UI token gates and the production Storybook build passed. - Greptile reviewed exact head `288364eada5d9c114fad991ff634612639d06672` at 5/5 with no actionable findings or unresolved review threads. - The full repository test suite and current-head CI are running. Their final results will replace this line before merge. - Earlier live feature previews created two bots in the authorized Paperclip Slack workspace and reused one personal manager grant. Local managed Continue created and installed a bot without a configuration token or a separate Add to Slack action. Avatars, welcome DMs, signed callbacks, and direct replies were observed. A Settings test DM also arrived in the signed-in user's Slack account. - Earlier staging thread qualification required manual publication recovery for one bot. Current-head live qualification remains required before enabling the production default. Fixture results do not establish Slack approval behavior in other workspaces. ## Risks - Personal manager tokens can create apps. They stay in scoped vault entries. The manager client secret stays at the broker. Company, actor/session, origin, and app identity checks apply throughout setup. - The additive migration must also work on qualification instances that applied earlier branch migrations. Table, column, index, and constraint creation is idempotent. - Creation and message timeouts can leave uncertain outcomes. Saved request IDs prevent automatic duplicate dispatch. - Unknown Slack identities must receive and complete their private challenge while signed in. Account selection alone does not grant Paperclip permissions. Existing revoked or conflicting links are not overwritten. - A development-only manager transport uses an explicitly configured private credential file for local qualification. Production ignores this transport; no manager credentials are bundled with open source. - Managed rollout requires Slack approval, the companion broker deployment, configured secrets, and final live qualification. Removing a connection does not delete its Slack app. ## Model Used OpenAI Codex, GPT-6 family, with reasoning, code execution, and browser tools. The session does not expose the exact serving model ID or context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- doc/DATABASE.md | 12 + doc/DEVELOPING.md | 9 + doc/connections/slack-setup.md | 162 ++++ .../src/migrations/0335_absurd_bulldozer.sql | 39 + ...{0330_snapshot.json => 0335_snapshot.json} | 821 +++++++++++++++++- packages/db/src/migrations/meta/_journal.json | 7 + .../src/schema/chat_slack_manager_grants.ts | 26 + .../db/src/schema/chat_slack_registrations.ts | 3 + packages/db/src/schema/index.ts | 2 + packages/shared/src/slack-app-manifest.ts | 45 +- packages/shared/src/types/chat-channels.ts | 6 +- .../shared/src/validators/chat-channels.ts | 5 +- .../chat-channels.integration.test.ts | 428 ++++++++- server/src/__tests__/http-log-policy.test.ts | 2 +- server/src/__tests__/redact-sensitive.test.ts | 5 + .../slack-development-manager.test.ts | 73 ++ server/src/middleware/http-log-policy.ts | 2 +- server/src/middleware/redact-sensitive.ts | 7 + server/src/routes/chat-channels.ts | 50 +- server/src/routes/openapi.ts | 40 +- server/src/services/chat-channels.ts | 94 +- .../chat-slack-registration-cleanup.ts | 5 +- .../src/services/chat-slack-registration.ts | 534 +----------- .../connectors/slack/setup/completion.ts | 164 ++++ .../connectors/slack/setup/customer-owned.ts | 139 +++ .../slack/setup/development-manager.ts | 73 ++ .../connectors/slack/setup/managed.ts | 81 ++ .../connectors/slack/setup/manager-grants.ts | 269 ++++++ .../connectors/slack/setup/provider-client.ts | 58 ++ .../connectors/slack/setup/registration.ts | 136 +++ .../connectors/slack/setup/service.ts | 151 ++++ .../services/connectors/slack/test-message.ts | 134 +++ .../paperclip-cloud-connector.test.ts | 17 +- .../src/services/paperclip-cloud-connector.ts | 19 +- tests/e2e/chat-adapters-ui-providers.spec.ts | 128 ++- tests/e2e/chat-adapters-ui.shared.ts | 20 +- ui/src/api/chatEndpoints.ts | 11 +- .../apps/chat/ChatEndpoint.clipboard.test.tsx | 22 +- ui/src/pages/apps/chat/ChatEndpointDetail.tsx | 52 +- ui/src/pages/apps/chat/ChatEndpointSetup.tsx | 107 ++- .../pages/apps/chat/SlackAutomaticSetup.tsx | 184 +--- ui/src/pages/apps/chat/SlackSetupPrompt.tsx | 8 +- .../apps/chat/SlackTestMessageButton.tsx | 88 ++ ui/src/pages/apps/chat/SlackToolSettings.tsx | 12 +- .../pages/apps/chat/chat-ui-contract.test.ts | 3 +- .../chat/slack/ManagedSlackSetup.test.tsx | 88 ++ .../apps/chat/slack/ManagedSlackSetup.tsx | 92 ++ .../apps/chat/slack/OwnSlackAppSetup.tsx | 185 ++++ ui/src/pages/apps/chat/slack/SlackSetup.tsx | 11 + .../pages/apps/chat/slack/setup-state.test.ts | 30 + ui/src/pages/apps/chat/slack/setup-state.ts | 30 + ui/storybook/fixtures/SlackSetupFixture.tsx | 33 +- .../chat-management.stories.tsx | 54 +- .../stories/slack-managed-setup.stories.tsx | 104 +++ 54 files changed, 3966 insertions(+), 914 deletions(-) create mode 100644 doc/connections/slack-setup.md create mode 100644 packages/db/src/migrations/0335_absurd_bulldozer.sql rename packages/db/src/migrations/meta/{0330_snapshot.json => 0335_snapshot.json} (98%) create mode 100644 packages/db/src/schema/chat_slack_manager_grants.ts create mode 100644 server/src/__tests__/slack-development-manager.test.ts create mode 100644 server/src/services/connectors/slack/setup/completion.ts create mode 100644 server/src/services/connectors/slack/setup/customer-owned.ts create mode 100644 server/src/services/connectors/slack/setup/development-manager.ts create mode 100644 server/src/services/connectors/slack/setup/managed.ts create mode 100644 server/src/services/connectors/slack/setup/manager-grants.ts create mode 100644 server/src/services/connectors/slack/setup/provider-client.ts create mode 100644 server/src/services/connectors/slack/setup/registration.ts create mode 100644 server/src/services/connectors/slack/setup/service.ts create mode 100644 server/src/services/connectors/slack/test-message.ts create mode 100644 ui/src/pages/apps/chat/SlackTestMessageButton.tsx create mode 100644 ui/src/pages/apps/chat/slack/ManagedSlackSetup.test.tsx create mode 100644 ui/src/pages/apps/chat/slack/ManagedSlackSetup.tsx create mode 100644 ui/src/pages/apps/chat/slack/OwnSlackAppSetup.tsx create mode 100644 ui/src/pages/apps/chat/slack/SlackSetup.tsx create mode 100644 ui/src/pages/apps/chat/slack/setup-state.test.ts create mode 100644 ui/src/pages/apps/chat/slack/setup-state.ts create mode 100644 ui/storybook/stories/slack-managed-setup.stories.tsx diff --git a/doc/DATABASE.md b/doc/DATABASE.md index 2177689e1b9..5862fe40a55 100644 --- a/doc/DATABASE.md +++ b/doc/DATABASE.md @@ -701,6 +701,18 @@ this non-secret binding for this namespace; Slack bot installation does not use PKCE. Removal and manual recovery invalidate outstanding attempts under the same credential-mutation lease used by configuration. +### Slack manager workspace grants + +`chat_slack_manager_grants` stores a personal workspace authorization, scoped by +company, Paperclip user, Slack user, workspace and manager app. Access/refresh +credentials are references to user-scoped vault entries, never plaintext columns. +Revision fencing protects reauthorization from concurrent revocation. Per-method +rate-limit deadlines retain Slack's Retry-After across setup retries. + +`chat_slack_registrations.manager_grant_id` is optional, with a composite company +foreign key. Existing registrations remain customer owned with a null reference. +Deleting one endpoint removes its registration credentials and attempts, but does +not revoke grants used by other bots. See [Slack setup](connections/slack-setup.md). ## Transaction-aware delivery work signals diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 233c2e5cf66..575b1430fa8 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2239,3 +2239,12 @@ Chromium. The test starts its own loopback Vite server and mocks API responses; it needs no running Paperclip instance or provider credentials. The same spec lives in the default `test:e2e` discovery tree, so the existing Chrome CI shards run it on pull requests. + +### Slack provisioning paths + +Self-hosted development continues to use independent Slack app setup. Managed +Slack installation is a disabled-by-default Cloud feature that also requires the +companion broker profile and Slack approval. Do not add manager client credentials +to a local `.env` or an open-source deployment. See +[Slack setup and Cloud qualification](connections/slack-setup.md) for the gates, +recovery behavior, and production-component stories. diff --git a/doc/connections/slack-setup.md b/doc/connections/slack-setup.md new file mode 100644 index 00000000000..78abb8ef67f --- /dev/null +++ b/doc/connections/slack-setup.md @@ -0,0 +1,162 @@ +# Slack app ownership and setup + +Slack has one chat runtime and two provisioning paths. A saved connection's +`slackSetupMethod` selects provisioning; it does not change tools, scopes, +conversation routing, people permissions, or channel access. + +| Method | App creation and installation | Default | +| --- | --- | --- | +| `managed` | A personal Slack manager grant creates a child app; managed installation supplies its bot token. Slack consent/admin approval remains a fallback. | New drafts on qualified Cloud deployments only | +| `automatic` | A temporary app configuration access token creates a customer-owned app; the user approves its OAuth installation. | New self-hosted drafts and Cloud without managed availability | +| `manual` | User creates an app from the canonical manifest and enters credentials. | Legacy drafts with no saved method | +| `existing` | User supplies credentials for an existing app. | Explicit selection | + +Existing drafts and active bots retain their method. Enabling managed setup never +adopts existing apps. Choose agent offers **Use your own app** under **Advanced**, below Slash command, +when the instance supports managed setup and creation has not started. The own-app flow retains **Create manually** and **Use an existing +app** under Advanced. After creation dispatch, a managed draft cannot switch paths. + +Managed setup has two visible stops: Choose agent and Connect. **Continue** starts +manager authorization or installation in the saved workspace automatically. Multiple +workspaces still require a selection. Recovery actions remain explicit; opening +a failed or uncertain registration never retries creation automatically. +Creation, installation, message waiting, and success use the same production +components and server evidence as the own-app flow. A click or OAuth return does +not constitute successful configuration. + +## Cloud deployment prerequisites + +Managed setup is disabled by default. Qualification requires all of: + +- The instance chat-connector rollout gate and connection-management permission. +- Verified Cloud runtime identity, connector broker signing/sealing configuration, + and valid independently configured public board and webhook HTTPS origins. +- `PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED=true` on the approved tenant deployment. +- The companion Cloud broker deployment with the explicitly enabled `slack.manager` + profile, approved Slack manager app, token rotation, and its client secret in + the broker's secret store. No manager client secret belongs in the tenant. + +The broker must advertise its manager app identity as well as the enabled profile. +Previously saved grants from a different manager app are not reused. A broker +without this capability leaves managed setup unavailable. Do not enable the tenant +gate until real Slack qualification has passed; fixture coverage does not prove +Marketplace eligibility or a workspace's approval policy. + +Self-hosted instances do not need a Paperclip account, broker enrollment, manager +credentials, or hosted-service calls. They continue using the own-app flow. + +## Personal workspace authorization + +Manager grants belong to a company **and one Paperclip user**, and bind a verified +Slack user, workspace, and manager app. Another company member must authorize their +own account, even for the same workspace. Access and rotating refresh tokens are +user-scoped vault entries. Refreshes serialize across connections, persist the new +refresh token, and require fresh authorization after an ambiguous exchange. + +Revoking a grant requests provider revocation, removes its local credentials, invalidates pending manager +authorization, and prevents future provisioning with it. The local activity records whether provider revocation succeeded. Provider errors never restore local access. It does not uninstall +apps or disable already-installed bots. Removing a bot deletes endpoint-specific +secrets and pending setup attempts; a reusable personal manager grant survives. +Provider-side app removal remains explicit through the saved Slack management URL. + +## Recovery + +- A dispatched creation with an unknown result is **uncertain**. Never automatically + create again. The user must inspect Slack and explicitly confirm that no app was + created before a new request identity is accepted. +- Denied or pending installation retains the app. Resume with the same grant or + complete the child app's state-bound OAuth approval; the installer must match the + grant's Slack identity before personal linking. +- Returned bot credentials are vaulted before inventory or runtime configuration. + Retry connecting reuses those credentials after network or inventory failures. + Rejected or under-scoped bot credentials instead reinstall the saved app; they + never trigger another app creation. +- Manifest updates finish before managed installation. Changes requiring updated + permissions therefore pass through installation again before activation. +- Expired or revoked manager grants require reconnection. Losing managed + availability pauses setup; it never changes the saved ownership method. +- Slack rate limits are grouped by manager/workspace/method. Provider `Retry-After` + is persisted across matching grants in the company, survives restarts, and blocks + early retries. Other tenant instances must observe Slack's limits independently; + no background loop retries app creation. + +All OAuth attempts bind company, actor/session, endpoint, registration revision, +and configured origins, expire after ten minutes, and are claimed once before +exchange. HTTP diagnostics redact manager tokens, refresh tokens, codes, and sealed +handoffs. Local activity records contain only IDs and safe outcome codes. This adds +no first-party telemetry. + +## Code and verification + +Server provisioning lives in `server/src/services/connectors/slack/setup/`; +`chat-slack-registration.ts` remains the compatibility facade. Shared completion +owns credential binding, personal linking, welcome messages, and verification. +Removal cleanup stays independently callable. Slack wizard components and stage +selection live in `ui/src/pages/apps/chat/slack/`. + +Production Storybook journeys: **Connections / Slack / Managed setup** and +**Connections / Slack / Automatic setup** (the persisted own-app automatic path). +Server integration cases cover personal grants, duplicate requests, uncertain +creation, approval fallback, refresh, revocation, removal, saved-credential recovery, +rate limits, and secret canaries. Browser cases are in +`tests/e2e/chat-adapters-ui-providers.spec.ts`; companion broker tests cover manager +user OAuth and the authenticated tenant handoff. + +Before enabling a deployment, create two test agents in the authorized workspace. +The second must reuse the personal grant without another configuration token or +manager authorization. Verify avatars, identity linking, signed inbound events, +and same-thread replies. Record real approval behavior separately from fixtures. + +### Managed setup during broker outages + +Cloud deployments configured for managed Slack keep the managed default when the broker profile is temporarily unavailable. New drafts remain on **Connect** and offer **Retry**. **Use your own app** remains available under **Advanced** on Choose agent before creation. Discovery loading or failure never selects the configuration-token page. Self-hosted instances without managed configuration still use their independent own-app flow; saved draft methods do not change automatically. Provisioning continues to require current broker availability. + +### Local managed-bot qualification + +Maintainers with an approved Slack manager app can exercise the real managed +journey from an authenticated local test drive exposed through HTTPS (including +TryCloudflare). This development transport talks directly to Slack and reuses +the production manager-grant vault, state claims, provisioning, completion, and +runtime. It does not exercise the Cloud broker's sealed handoff; qualify that +separately on staging afterward. + +Set `NODE_ENV=development`, `PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED=true`, and +`PAPERCLIP_SLACK_MANAGER_DEVELOPMENT_CONFIG` to an absolute path outside the +checkout containing a mode-0600 JSON file with `appId`, `clientId`, and +`clientSecret`. Use only an authorized qualification manager app. No credentials +are shipped with Paperclip. Production ignores this development configuration, +and Cloud runtime identity continues to select the Cloud broker. + +Configure the local instance for authenticated public access, with its public +URL and chat webhook URL pointing to the HTTPS tunnel. Add the exact public URL +plus `/api/chat-slack/managed/oauth/callback` to the manager app's OAuth redirect +URLs, preserving any existing redirects. Start a **new** Slack setup: saved +own-app drafts retain their selected method. Authorize the manager app normally; +do not copy user grants from another instance. The second bot should reuse the +same local user's vaulted grant. A changed tunnel requires updating public +origins and the manager redirect before starting fresh authorization. + +Remove the temporary redirect when retiring the test drive and securely remove +its development credential file. Removing a bot does not revoke a manager grant +used by other bots. + +### Test messages from Settings + +**Send me a test message** sends a DM to the signed-in connection manager, +using their linked identity in this workspace. If this bot is not linked yet, +Paperclip can reuse that user's verified manager grant or another bot's linked +identity in the same company and workspace. Otherwise, the user selects their +Slack account and receives a private connection invitation in Slack. They must +open that invitation, sign in, and confirm before Paperclip links the account and +sends the test DM. The account picker loads further workspace pages on request. Existing revoked or conflicting identity links are not overwritten. +Direct messages must be enabled, and the user must be an active company operator. + +The action records a durable request before sending. Repeating the same request +returns its saved result; an uncertain delivery is never automatically resent. +**Open Slack configuration** opens the saved app's OAuth & Permissions page. +Legacy connections without an app ID open Slack's app list instead. + +An explicit managed reconnect reinstalls the saved app and verifies the replacement +bot credentials. Its request ID is saved before dispatch, so a duplicate completed +request does not reinstall again. Credential-configuration recovery still resumes +the saved token after a downstream failure. No reconnect creates another Slack app. diff --git a/packages/db/src/migrations/0335_absurd_bulldozer.sql b/packages/db/src/migrations/0335_absurd_bulldozer.sql new file mode 100644 index 00000000000..56224dbe057 --- /dev/null +++ b/packages/db/src/migrations/0335_absurd_bulldozer.sql @@ -0,0 +1,39 @@ +CREATE TABLE IF NOT EXISTS "chat_slack_manager_grants" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "user_id" text NOT NULL, + "slack_user_id" text NOT NULL, + "workspace_id" text NOT NULL, + "workspace_name" text NOT NULL, + "manager_app_id" text NOT NULL, + "status" text DEFAULT 'active' NOT NULL, + "access_secret_id" uuid, + "refresh_secret_id" uuid, + "expires_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "chat_slack_manager_grants_status_check" CHECK ("chat_slack_manager_grants"."status" in ('active', 'revoked', 'reauthorize')) +); + +--> statement-breakpoint + +ALTER TABLE "chat_slack_registrations" ADD COLUMN IF NOT EXISTS "manager_grant_id" uuid; +--> statement-breakpoint +DO $$ BEGIN +ALTER TABLE "chat_slack_manager_grants" ADD CONSTRAINT "chat_slack_manager_grants_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION WHEN duplicate_object THEN NULL; END $$; +--> statement-breakpoint + +CREATE UNIQUE INDEX IF NOT EXISTS "chat_slack_manager_grants_company_id_idx" ON "chat_slack_manager_grants" USING btree ("company_id","id"); +--> statement-breakpoint + +CREATE UNIQUE INDEX IF NOT EXISTS "chat_slack_manager_grants_owner_idx" ON "chat_slack_manager_grants" USING btree ("company_id","user_id","manager_app_id","workspace_id"); +--> statement-breakpoint +DO $$ BEGIN +ALTER TABLE "chat_slack_registrations" ADD CONSTRAINT "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk" FOREIGN KEY ("company_id","manager_grant_id") REFERENCES "public"."chat_slack_manager_grants"("company_id","id") ON DELETE no action ON UPDATE no action; +EXCEPTION WHEN duplicate_object THEN NULL; END $$; +--> statement-breakpoint +ALTER TABLE "chat_slack_manager_grants" ADD COLUMN IF NOT EXISTS "revision" integer DEFAULT 0 NOT NULL; +--> statement-breakpoint + +ALTER TABLE "chat_slack_manager_grants" ADD COLUMN IF NOT EXISTS "rate_limits" jsonb DEFAULT '{}'::jsonb NOT NULL; diff --git a/packages/db/src/migrations/meta/0330_snapshot.json b/packages/db/src/migrations/meta/0335_snapshot.json similarity index 98% rename from packages/db/src/migrations/meta/0330_snapshot.json rename to packages/db/src/migrations/meta/0335_snapshot.json index 460bb97a1cf..b348334bbd3 100644 --- a/packages/db/src/migrations/meta/0330_snapshot.json +++ b/packages/db/src/migrations/meta/0335_snapshot.json @@ -1,6 +1,6 @@ { - "id": "0a93bc99-bf0c-4ea2-ab27-6043b6746cd5", - "prevId": "76861d12-d9a9-4f5f-bcbc-0fb06c0addbc", + "id": "18c156f2-8344-4282-98b7-f702f0035c81", + "prevId": "39d82f2c-8fd5-4606-84df-f51880dbd6a7", "version": "7", "dialect": "postgresql", "tables": { @@ -416,6 +416,12 @@ "primaryKey": false, "notNull": false }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, "agent_id": { "name": "agent_id", "type": "uuid", @@ -462,6 +468,27 @@ } }, "indexes": { + "budget_reservations_fast_response_idx": { + "name": "budget_reservations_fast_response_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fast_response_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, "budget_reservations_decision_idx": { "name": "budget_reservations_decision_idx", "columns": [ @@ -580,7 +607,7 @@ "checkConstraints": { "budget_reservations_source_check": { "name": "budget_reservations_source_check", - "value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int = 1" + "value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int + (\"budget_reservations\".\"fast_response_request_id\" is not null)::int = 1" }, "budget_reservations_state_check": { "name": "budget_reservations_state_check", @@ -3737,6 +3764,28 @@ "method": "btree", "with": {} }, + "agent_wakeup_requests_question_response_history_idx": { + "name": "agent_wakeup_requests_question_response_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%'", + "concurrently": true, + "method": "btree", + "with": {} + }, "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", "columns": [ @@ -12384,6 +12433,189 @@ }, "isRLSEnabled": false }, + "public.chat_slack_manager_grants": { + "name": "chat_slack_manager_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_app_id": { + "name": "manager_app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "access_secret_id": { + "name": "access_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_secret_id": { + "name": "refresh_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rate_limits": { + "name": "rate_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_slack_manager_grants_company_id_idx": { + "name": "chat_slack_manager_grants_company_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_slack_manager_grants_owner_idx": { + "name": "chat_slack_manager_grants_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manager_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_slack_manager_grants_company_id_companies_id_fk": { + "name": "chat_slack_manager_grants_company_id_companies_id_fk", + "tableFrom": "chat_slack_manager_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_manager_grants_status_check": { + "name": "chat_slack_manager_grants_status_check", + "value": "\"chat_slack_manager_grants\".\"status\" in ('active', 'revoked', 'reauthorize')" + } + }, + "isRLSEnabled": false + }, "public.chat_slack_registrations": { "name": "chat_slack_registrations", "schema": "", @@ -12400,6 +12632,12 @@ "primaryKey": false, "notNull": true }, + "manager_grant_id": { + "name": "manager_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, "request_id": { "name": "request_id", "type": "uuid", @@ -12490,6 +12728,21 @@ }, "indexes": {}, "foreignKeys": { + "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_slack_manager_grants", + "columnsFrom": [ + "company_id", + "manager_grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { "name": "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", "tableFrom": "chat_slack_registrations", @@ -16725,6 +16978,19 @@ "primaryKey": false, "notNull": false }, + "package_mode": { + "name": "package_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skills'" + }, + "repository_package": { + "name": "repository_package", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "excluded_folders": { "name": "excluded_folders", "type": "jsonb", @@ -17052,6 +17318,87 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.company_skill_repository_snapshots": { + "name": "company_skill_repository_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_repository_snapshots_content_idx": { + "name": "company_skill_repository_snapshots_content_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_repository_snapshots_company_id_companies_id_fk": { + "name": "company_skill_repository_snapshots_company_id_companies_id_fk", + "tableFrom": "company_skill_repository_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.company_skill_stars": { "name": "company_skill_stars", "schema": "", @@ -17906,6 +18253,18 @@ "primaryKey": false, "notNull": true }, + "repository_snapshot_id": { + "name": "repository_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_skill_path": { + "name": "repository_skill_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, "revision_number": { "name": "revision_number", "type": "integer", @@ -18062,6 +18421,19 @@ "onDelete": "cascade", "onUpdate": "no action" }, + "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk": { + "name": "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skill_repository_snapshots", + "columnsFrom": [ + "repository_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, "company_skill_versions_author_agent_id_agents_id_fk": { "name": "company_skill_versions_author_agent_id_agents_id_fk", "tableFrom": "company_skill_versions", @@ -19576,6 +19948,34 @@ "method": "btree", "with": {} }, + "cost_events_data_quality_idx": { + "name": "cost_events_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"billing_type\" = 'unknown' or (\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included')", + "concurrently": false, + "method": "btree", + "with": {} + }, "cost_events_company_occurred_idx": { "name": "cost_events_company_occurred_idx", "columns": [ @@ -19826,7 +20226,7 @@ "checkConstraints": { "cost_events_usage_kind_check": { "name": "cost_events_usage_kind_check", - "value": "\"cost_events\".\"usage_kind\" = 'decision' or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)" + "value": "\"cost_events\".\"usage_kind\" in ('decision', 'fast_response') or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)" }, "cost_events_nonnegative_amounts": { "name": "cost_events_nonnegative_amounts", @@ -27491,6 +27891,378 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.company_fast_responses": { + "name": "company_fast_responses", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_sponsored": { + "name": "allow_sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_fast_responses_company_id_companies_id_fk": { + "name": "company_fast_responses_company_id_companies_id_fk", + "tableFrom": "company_fast_responses", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_fast_responses_configured": { + "name": "company_fast_responses_configured", + "value": "(\"company_fast_responses\".\"connection_id\" is null) = (\"company_fast_responses\".\"grant_id\" is null) and (not \"company_fast_responses\".\"enabled\" or (\"company_fast_responses\".\"connection_id\" is not null and \"company_fast_responses\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.fast_response_requests": { + "name": "fast_response_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sponsored": { + "name": "sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "publication_status": { + "name": "publication_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_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 + }, + "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 + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "fast_response_requests_source_idx": { + "name": "fast_response_requests_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_history_idx": { + "name": "fast_response_requests_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_work_idx": { + "name": "fast_response_requests_work_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"fast_response_requests\".\"status\" in ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fast_response_requests_company_id_companies_id_fk": { + "name": "fast_response_requests_company_id_companies_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fast_response_requests_cost_event_id_cost_events_id_fk": { + "name": "fast_response_requests_cost_event_id_cost_events_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "fast_response_requests_status_check": { + "name": "fast_response_requests_status_check", + "value": "\"fast_response_requests\".\"status\" in ('pending', 'running', 'succeeded', 'failed', 'unknown', 'skipped')" + } + }, + "isRLSEnabled": false + }, "public.feedback_exports": { "name": "feedback_exports", "schema": "", @@ -29938,6 +30710,34 @@ "method": "btree", "with": {} }, + "heartbeat_runs_cost_data_quality_idx": { + "name": "heartbeat_runs_cost_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"finished_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true and \"heartbeat_runs\".\"status\" in ('succeeded','failed','timed_out','cancelled','interrupted')", + "concurrently": false, + "method": "btree", + "with": {} + }, "heartbeat_runs_execution_status_delivery_idx": { "name": "heartbeat_runs_execution_status_delivery_idx", "columns": [ @@ -32392,6 +33192,19 @@ "primaryKey": false, "notNull": false }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'comment'" + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, "body": { "name": "body", "type": "text", diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 4b1ef9079c1..22c832f0017 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -2332,6 +2332,13 @@ "when": 1791807836288, "tag": "0334_rare_slipstream", "breakpoints": true + }, + { + "idx": 335, + "version": "7", + "when": 1791832982875, + "tag": "0335_absurd_bulldozer", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/chat_slack_manager_grants.ts b/packages/db/src/schema/chat_slack_manager_grants.ts new file mode 100644 index 00000000000..fb4d5ef8861 --- /dev/null +++ b/packages/db/src/schema/chat_slack_manager_grants.ts @@ -0,0 +1,26 @@ +import { sql } from "drizzle-orm"; +import { check, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; + +/** Reusable personal management authorization; never a bot runtime credential. */ +export const chatSlackManagerGrants = pgTable("chat_slack_manager_grants", { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + userId: text("user_id").notNull(), + slackUserId: text("slack_user_id").notNull(), + workspaceId: text("workspace_id").notNull(), + workspaceName: text("workspace_name").notNull(), + managerAppId: text("manager_app_id").notNull(), + status: text("status").$type<"active" | "revoked" | "reauthorize">().notNull().default("active"), + accessSecretId: uuid("access_secret_id"), + refreshSecretId: uuid("refresh_secret_id"), + revision: integer("revision").notNull().default(0), + rateLimits: jsonb("rate_limits").$type>().notNull().default({}), + expiresAt: timestamp("expires_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, t => [ + uniqueIndex("chat_slack_manager_grants_company_id_idx").on(t.companyId, t.id), + uniqueIndex("chat_slack_manager_grants_owner_idx").on(t.companyId, t.userId, t.managerAppId, t.workspaceId), + check("chat_slack_manager_grants_status_check", sql`${t.status} in ('active', 'revoked', 'reauthorize')`), +]); diff --git a/packages/db/src/schema/chat_slack_registrations.ts b/packages/db/src/schema/chat_slack_registrations.ts index 0edad4a8917..5d17b660bd0 100644 --- a/packages/db/src/schema/chat_slack_registrations.ts +++ b/packages/db/src/schema/chat_slack_registrations.ts @@ -1,12 +1,14 @@ import { sql } from "drizzle-orm"; import { check, foreignKey, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import type { SlackRegistrationStatus } from "@paperclipai/shared"; +import { chatSlackManagerGrants } from "./chat_slack_manager_grants.js"; import { chatEndpoints } from "./chat_channels.js"; /** App provisioning is distinct from installation and runtime credentials. No plaintext secrets. */ export const chatSlackRegistrations = pgTable("chat_slack_registrations", { endpointId: uuid("endpoint_id").primaryKey(), companyId: uuid("company_id").notNull(), + managerGrantId: uuid("manager_grant_id"), requestId: uuid("request_id").notNull(), status: text("status").$type().notNull(), manifest: jsonb("manifest").$type>().notNull(), @@ -22,6 +24,7 @@ export const chatSlackRegistrations = pgTable("chat_slack_registrations", { createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), }, t => [ + foreignKey({ columns: [t.companyId, t.managerGrantId], foreignColumns: [chatSlackManagerGrants.companyId, chatSlackManagerGrants.id] }), foreignKey({ columns: [t.companyId, t.endpointId], foreignColumns: [chatEndpoints.companyId, chatEndpoints.id] }).onDelete("cascade"), check("chat_slack_registration_status_check", sql`${t.status} in ('creating', 'uncertain', 'failed', 'install', 'credentials_saved', 'configured', 'removed')`), ]); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 4c4bad38337..630a0869b55 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -243,6 +243,8 @@ export { chatVoiceReports } from "./chat_voice_reports.js"; export { chatVoicePhoneLines, chatVoiceInboundCalls } from "./chat_voice_inbound.js"; export * from "./decision_models.js"; export { userCompanyPreferences } from "./user_company_preferences.js"; + +export { chatSlackManagerGrants } from "./chat_slack_manager_grants.js"; export { aiSubscriptions, aiSubscriptionPrices, aiSubscriptionConnections } from "./ai_subscriptions.js"; export * from "./fast_responses.js"; diff --git a/packages/shared/src/slack-app-manifest.ts b/packages/shared/src/slack-app-manifest.ts index d8ec7d59ee0..e6d3f251424 100644 --- a/packages/shared/src/slack-app-manifest.ts +++ b/packages/shared/src/slack-app-manifest.ts @@ -22,7 +22,7 @@ export function buildSlackAppManifest(input: { redirectUri?: string; }) { return { - display_information: { name: input.app.appName }, + display_information: { name: input.app.appName, description: `Talk to ${input.agentName} and manage work in Paperclip.`.slice(0, 140) }, features: { app_home: { home_tab_enabled: false, messages_tab_enabled: true, messages_tab_read_only_enabled: false }, agent_view: { agent_description: "Work with a Paperclip agent in a task-backed conversation." }, @@ -60,6 +60,7 @@ export const slackRegistrationStateSchema = z.object({ status: slackRegistrationStatusSchema, appId: z.string().nullable().optional(), managementUrl: z.string().url().nullable().optional(), + managerGrantId: z.string().uuid().nullable().optional(), errorCode: z.string().nullable().optional(), }).strict(); export type SlackRegistrationState = z.infer; @@ -79,11 +80,33 @@ export const slackAccountStateSchema = z.object({ }).strict(); export type SlackAccountState = z.infer; export const slackInstallAuthorizationSchema = z.object({ authorizationUrl: z.string().url(), expiresAt: z.string().datetime() }); +export const slackManagerAuthorizationSchema = slackInstallAuthorizationSchema.extend({ handoff: z.object({ kind: z.literal("paperclip_cloud"), session: z.string().min(16).max(512) }).optional() }); +export type SlackManagerAuthorization = z.infer; export type SlackInstallAuthorization = z.infer; export const slackSetupActionSchema = z.object({}).strict(); +export const slackTestMessageSchema = z.object({ + requestId: z.string().uuid(), + slackUserId: z.string().regex(/^[UW][A-Z0-9]+$/).optional(), + accountCursor: z.string().min(1).max(512).optional(), +}).strict(); +export type SlackTestMessageInput = z.infer; +export const slackTestMessageResultSchema = z.discriminatedUnion("status", [ + z.object({ status: z.literal("sent"), slackUrl: z.string().url() }).strict(), + z.object({ status: z.literal("connection_required"), accounts: z.array(z.object({ + id: z.string(), name: z.string(), + }).strict()), nextCursor: z.string().nullable() }).strict(), + z.object({ status: z.literal("connection_pending"), principalId: z.string().uuid(), expiresAt: z.string().datetime() }).strict(), +]); +export type SlackTestMessageResult = z.infer; + export function slackRegistrationErrorMessage(code: string): string { const messages: Record = { + slack_managed_unavailable: "Managed Slack setup is unavailable. Try again later or use your own app before creation starts.", + slack_manager_reauthorize: "Reconnect your Slack workspace to continue setting up this agent.", + slack_approval_pending: "Slack is waiting for your workspace administrator to approve this app.", + slack_approval_required: "Your Slack workspace requires approval. Continue in Slack to request it.", + slack_approval_denied: "Your Slack administrator declined this installation. Contact them to continue.", slack_configuration_token_invalid: "Slack rejected the app configuration access token. Generate a new access token and try again.", slack_manifest_invalid: "Slack rejected this app configuration. Check the app details or use manual setup.", slack_manifest_update_pending: "Your Slack app is saved, but its event settings still need to be applied. Enter an app configuration access token to retry configuring the same app.", @@ -96,9 +119,29 @@ export function slackRegistrationErrorMessage(code: string): string { slack_install_identity_mismatch: "Slack returned a different app, workspace, or bot. Install the app created for this connection.", slack_install_scopes_missing: "Slack did not grant all required bot permissions. Install the app again and approve its requested permissions.", slack_bot_already_connected: "This Slack bot is already connected to Paperclip. Resume its existing connection or use a different app.", + slack_install_token_invalid: "Slack rejected the saved bot credentials. Continue in Slack to authorize this app again.", slack_configuration_incomplete: "The app is installed, but Paperclip could not finish connecting it. Retry connecting the saved installation.", slack_install_account_missing: "Slack did not identify your account. Authorize the same app again to connect your Slack account.", slack_install_account_conflict: "This Slack account already has an existing link. Manage account links in connection settings, or authorize with your own Slack account.", }; return messages[code] ?? "Slack setup could not be completed. Resume setup and try again."; } + +/** Persisted legacy values keep their original meaning. Missing means manual. */ +export type SlackSetupMethod = "managed" | "automatic" | "manual" | "existing"; +export function isProvisionedSlackSetup(method: string | undefined): boolean { + return method === "automatic" || method === "managed"; +} +export const SLACK_MANAGER_SCOPES = ["app_configurations:read", "app_configurations:write", "managed_apps:install"] as const; +export const slackSetupOptionsSchema = z.object({ + managedAvailable: z.boolean(), + defaultMethod: z.enum(["managed", "automatic"]), + workspaces: z.array(z.object({ grantId: z.string().uuid(), workspaceId: z.string(), workspaceName: z.string(), userId: z.string() })), +}); +export type SlackSetupOptions = z.infer; +export const slackManagedProvisionSchema = z.object({ + requestId: z.string().uuid(), grantId: z.string().uuid(), confirmedNoAppCreated: z.boolean().optional(), reconnect: z.boolean().optional(), +}).strict(); +export type SlackManagedProvisionInput = z.infer; +export const slackManagedResultSchema = z.object({ authorization: slackInstallAuthorizationSchema.optional() }); +export type SlackManagedResult = z.infer; diff --git a/packages/shared/src/types/chat-channels.ts b/packages/shared/src/types/chat-channels.ts index af49abfb8da..5ebcda06a2b 100644 --- a/packages/shared/src/types/chat-channels.ts +++ b/packages/shared/src/types/chat-channels.ts @@ -190,7 +190,8 @@ export interface SlackAppConfiguration { } export interface ChatEndpointSetupState { - slackSetupMethod?: "automatic" | "manual" | "existing"; + slackSetupMethod?: "managed" | "automatic" | "manual" | "existing"; + slackManagerError?: string; slackRegistration?: import("../slack-app-manifest.js").SlackRegistrationState; /** Provider-confirmed app icon outcome; configuration tokens are never retained. */ slackAvatar?: import("../slack-app-manifest.js").SlackAvatarState; @@ -508,10 +509,11 @@ export interface CreateChatEndpointInput { applicationId?: string; name?: string; slackApp?: SlackAppConfiguration; + slackSetupMethod?: "managed" | "automatic" | "manual" | "existing"; } export interface UpdateChatEndpointInput { - slackSetupMethod?: "automatic" | "manual" | "existing"; + slackSetupMethod?: "managed" | "automatic" | "manual" | "existing"; slackApp?: SlackAppConfiguration; communicationInstructions?: string; allowDirectMessages?: boolean; diff --git a/packages/shared/src/validators/chat-channels.ts b/packages/shared/src/validators/chat-channels.ts index f2553588ef3..53b263462f0 100644 --- a/packages/shared/src/validators/chat-channels.ts +++ b/packages/shared/src/validators/chat-channels.ts @@ -80,9 +80,10 @@ export const createChatEndpointSchema = z applicationId: z.string().uuid().optional(), name: z.string().trim().min(1).max(160).optional(), slackApp: slackAppConfigurationSchema.optional(), + slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(), }) .strict() - .refine(value => !value.slackApp || value.provider === "slack", { + .refine(value => (!value.slackApp && !value.slackSetupMethod) || value.provider === "slack", { message: "Slack app details only apply to Slack connections", path: ["slackApp"], }); @@ -90,7 +91,7 @@ export const updateChatEndpointSchema = z .object({ communicationInstructions: multilineTextSchema.pipe(z.string().trim().max(4000)).optional(), slackApp: slackAppConfigurationSchema.optional(), - slackSetupMethod: z.enum(["automatic", "manual", "existing"]).optional(), + slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(), allowDirectMessages: z.boolean().optional(), requireAtMention: z.boolean().optional(), allowGroupChats: z.boolean().optional(), diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index 8b8b0f29641..6893a548784 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -1,3 +1,7 @@ +import * as activityLogModule from "../services/activity-log.js"; +import { chatSlackManagerGrants, chatSlackRegistrations, toolOauthStates } from "@paperclipai/db"; +import type { PaperclipCloudConnector, SealedConnectorCredentials } from "../services/paperclip-cloud-connector.js"; +import { buildSlackAppManifest, SLACK_MANAGER_SCOPES } from "@paperclipai/shared"; import { registerChatActionWork, dueChatAction } from "../services/chat-action-work.js"; import { createDeliveryWorkCoordinator } from "../services/delivery-work-coordinator.js"; import { registerChatDeliveryWork } from "../services/chat-delivery-work.js"; @@ -7,14 +11,11 @@ import { aiConnectionService } from "../services/ai-connections.js"; import { fastResponseService } from "../services/fast-responses.js"; import { fastResponseReceipt } from "../services/fast-response-provider.js"; import { fastResponseRequests } from "@paperclipai/db"; -import { chatSlackRegistrations, toolOauthStates } from "@paperclipai/db"; -import { buildSlackAppManifest } from "@paperclipai/shared"; import { toolAccessService } from "../services/tool-access.js"; import { toolActionRequests, toolInvocations } from "@paperclipai/db"; import { GitHubPublicationLeaseLost, withGitHubPublicationLease } from "../services/chat-github-publication-lease.js"; import { secretService } from "../services/secrets.js"; import { githubChatWizardService } from "../services/chat-github-wizard.js"; -import type { PaperclipCloudConnector } from "../services/paperclip-cloud-connector.js"; import { dispatchGitHubBotCloudEvent } from "../services/chat-github-cloud-ingress.js"; import { githubChatManagementService } from "../services/chat-github-management.js"; import { githubChatReviewService } from "../services/chat-github-reviews.js"; @@ -6262,7 +6263,9 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const token = `xoxb-bot-token-canary-${id}`; const scopes = buildSlackAppManifest({ app: appDetails, agentName: "Maya", webhookUrl: "https://paperclip.example/hook" }).oauth_config.scopes.bot; const state = { - failCreate: false, failValidate: false, failInventory: false, failAuth: false, failExchange: false, + managedAccessToken: token, rejectOldBotToken: false, + userListCursor: null as string | null, userListNextCursor: "", + managedBotAppId: appId, grantedScopes: scopes, managedError: null as string | null, failCreate: false, failValidate: false, failInventory: false, failAuth: false, failExchange: false, failManifestUpdate: false, timeoutManifestUpdate: false, beforeManifestUpdate: null as null | (() => Promise), failIcon: false, timeoutIcon: false, failUser: false, failWelcome: false, timeoutWelcome: false, @@ -6270,20 +6273,23 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { user: { id: installerId, team_id: "TAUTO", name: "installer", real_name: "Installing Person", is_bot: false, deleted: false }, beforeIcon: null as null | (() => Promise), beforeAuth: null as null | (() => Promise), + beforeExchange: null as null | (() => Promise), oauth: { ok: true, app_id: appId, token_type: "bot", access_token: token, team: { id: "TAUTO" }, bot_user_id: botId, authed_user: { id: installerId }, scope: scopes.join(",") }, }; const fallback = fakeSlackFetch(botId); const provider = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { const url = String(input); + if (url.endsWith("bots.info")) return Response.json({ ok: true, bot: { id: "BMANAGED", app_id: state.managedBotAppId, user_id: botId, deleted: false } }); + if (url.endsWith("apps.managedInstall")) return Response.json(state.managedError ? { ok: false, error: state.managedError } : { ok: true, app_id: appId, team_id: "TAUTO", api_access_tokens: { bot_access_token: state.managedAccessToken } }); if (url.endsWith("apps.manifest.validate")) return Response.json(state.failValidate ? { ok: false, error: "invalid_auth", detail: configToken } : { ok: true }); if (url.endsWith("apps.manifest.create")) { if (state.failCreate) throw new Error(`Timeout ${configToken} ${clientSecret}`); - return Response.json({ ok: true, app_id: appId, credentials: { client_id: "123.456", client_secret: clientSecret, signing_secret: signingSecret } }); + return Response.json({ ok: true, app_id: appId, team_id: "TAUTO", credentials: { client_id: "123.456", client_secret: clientSecret, signing_secret: signingSecret } }); } if (url.endsWith("apps.manifest.update")) { await state.beforeManifestUpdate?.(); if (state.timeoutManifestUpdate) throw new Error(`Manifest timeout ${configToken} ${clientSecret}`); - return Response.json(state.failManifestUpdate ? { ok: false, error: "invalid_auth", detail: configToken } : { ok: true, app_id: appId }); + return Response.json(state.failManifestUpdate ? { ok: false, error: "invalid_auth", detail: configToken } : { ok: true, app_id: appId, permissions_updated: true }); } if (url.endsWith("apps.icon.set")) { await state.beforeIcon?.(); @@ -6291,13 +6297,19 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { return Response.json(state.failIcon ? { ok: false, error: "missing_scope", detail: `${configToken} ${signingSecret}` } : { ok: true }); } if (url.endsWith("oauth.v2.access")) { + await state.beforeExchange?.(); if (state.failExchange) throw new Error(`Timeout ${clientSecret} ${token}`); return Response.json(state.oauth); } if (url.endsWith("auth.test")) { await state.beforeAuth?.(); + if (state.rejectOldBotToken && new Headers(init?.headers).get("authorization") !== `Bearer ${state.managedAccessToken}`) return Response.json({ ok: false, error: "token_revoked" }); if (state.failAuth) throw new Error(`Temporary failure ${token}`); - return Response.json({ ok: true, team_id: "TAUTO", user_id: botId, user: `maya-${id}`, team: "Test workspace" }, { headers: { "x-oauth-scopes": scopes.join(",") } }); + return Response.json({ ok: true, team_id: "TAUTO", user_id: botId, bot_id: "BMANAGED", user: `maya-${id}`, team: "Test workspace" }, { headers: { "x-oauth-scopes": state.grantedScopes.join(",") } }); + } + if (url.endsWith("users.list")) { + state.userListCursor = new URLSearchParams(String(init?.body)).get("cursor"); + return Response.json({ ok: true, members: [state.user], response_metadata: { next_cursor: state.userListNextCursor } }); } if (url.endsWith("users.info")) { await state.beforeUser?.(); @@ -6323,6 +6335,408 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const install = async () => new URL((await service.slackRegistration.install(endpoint.id, actor)).authorizationUrl).searchParams.get("state")!; return { ...company, endpoint, service, sdk, app, state, provider, create, install, createInput, token, appId, botId, installerId, avatarBytes, renderAvatar }; } + async function managedFixture() { + const broker = { + getCapabilities: vi.fn(async () => ["slack.manager"]), + getSlackManagerAppId: vi.fn(async () => "AMANAGER"), + startAuthorization: vi.fn(async (_input: { returnState: string }) => ({ authorizationUrl: "https://slack.com/oauth/v2/authorize?state=broker", expiresAt: new Date(Date.now() + 600_000).toISOString() })), + claim: vi.fn(), refresh: vi.fn(), revoke: vi.fn(async () => undefined), + }; + const f = await fixture({ slackManaged: { enabled: true, broker: broker as unknown as PaperclipCloudConnector } }); + const credentials: SealedConnectorCredentials = { v: 1, provider: "slack", profile: "slack.manager", instanceId: "test", environment: "staging", subject: actor.userId, companyId: f.companyId, + accessToken: "xoxe.xoxp-manager-access-canary", refreshToken: "xoxe-manager-refresh-canary", tokenType: "user", accessTokenExpiresAt: new Date(Date.now() + 43_200_000).toISOString(), refreshTokenExpiresAt: null, + scopes: [...SLACK_MANAGER_SCOPES], slackManager: { appId: "AMANAGER", workspaceId: "TAUTO", workspaceName: "Test workspace", userId: f.installerId } }; + broker.claim.mockResolvedValue(credentials); + broker.refresh.mockResolvedValue({ ...credentials, accessToken: "xoxe.rotated-canary", refreshToken: "xoxe.rotated-refresh-canary" }); + await f.service.update(f.endpoint.id, { slackSetupMethod: "managed" }, actor.userId); + const grants = f.service.slackRegistration.managerGrants; + const authorize = async () => { await grants.authorize(f.endpoint.id, actor); return broker.startAuthorization.mock.calls.at(-1)![0].returnState; }; + const authorizeState = await authorize(); + await grants.complete(authorizeState, "claim-canary", null, actor); + const choices = await grants.choices(f.companyId, actor); + expect(choices.workspaces).toHaveLength(1); + const grantId = choices.workspaces[0]!.grantId; + const input = { requestId: randomUUID(), grantId }; + const provision = () => f.service.slackRegistration.managed.provision(f.endpoint.id, actor, input); + return { ...f, broker, credentials, grants, authorize, authorizeState, grantId, input, provision }; + } + describe("Slack test messages", () => { + const posts = (f: Awaited>) => f.provider.mock.calls.filter(([url]) => String(url).endsWith("chat.postMessage")); + it("sends to the current linked user and reuses a durable result on duplicate requests", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + const input = { requestId: randomUUID() }; + const result = await request(f.app).post(`/api/chat-endpoints/${f.endpoint.id}/slack/test-message`).send(input).expect(200); + expect(result.body).toEqual({ status: "sent", slackUrl: "https://app.slack.com/client/TAUTO/DWELCOME" }); + await f.service.sendSlackTestMessage(f.endpoint.id, actor, input); + expect(posts(f)).toHaveLength(1); + expect(new URLSearchParams(String(posts(f)[0]![1]!.body)).get("channel")).toBe(f.installerId); + }); + it("links the actor's own manager identity without requiring a connect command", async () => { + const f = await managedFixture(); await f.provision(); + await db.delete(chatIdentityLinks).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID() })).toMatchObject({ status: "sent" }); + expect(await db.select().from(chatIdentityLinks).where(eq(chatIdentityLinks.endpointId, f.endpoint.id))).toEqual([expect.objectContaining({ paperclipUserId: actor.userId, status: "linked" })]); + }); + it("requires a private Slack challenge for an unknown account before linking and sending the test", async () => { + const f = await managedFixture(); await f.provision(); + await db.delete(chatIdentityLinks).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + await db.update(chatSlackManagerGrants).set({ userId: "another-user" }).where(eq(chatSlackManagerGrants.id, f.grantId)); + f.provider.mockClear(); const input = { requestId: randomUUID() }; + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toEqual({ status: "connection_required", accounts: [{ id: f.installerId, name: "Installing Person" }], nextCursor: null }); + expect(posts(f)).toHaveLength(0); + const pending = await f.service.sendSlackTestMessage(f.endpoint.id, actor, { ...input, slackUserId: f.installerId }); + expect(pending.status).toBe("connection_pending"); + const links = await db.select().from(chatIdentityLinks).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + expect(links).toEqual([expect.objectContaining({ status: "pending", paperclipUserId: null })]); + const fields = new URLSearchParams(String(posts(f)[0]![1]!.body)); + expect(fields.get("text")).not.toContain("This is your test message"); + const blocks = JSON.parse(fields.get("blocks")!); + const invitationUrl = /<([^|]+)\|/.exec(blocks[1].text.text)![1]!; + const token = new URL(invitationUrl).searchParams.get("token")!; + expect(JSON.stringify(pending)).not.toContain(token); + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toEqual(pending); + expect(posts(f)).toHaveLength(1); + await f.service.confirmIdentityLink(token, actor.userId); + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toMatchObject({ status: "sent" }); + expect(new URLSearchParams(String(posts(f).at(-1)![1]!.body)).get("text")).toContain("This is your test message"); + }); + it("loads another workspace page instead of truncating account discovery", async () => { + const f = await managedFixture(); await f.provision(); + await db.delete(chatIdentityLinks).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + await db.update(chatSlackManagerGrants).set({ userId: "another-user" }).where(eq(chatSlackManagerGrants.id, f.grantId)); + f.provider.mockClear(); f.state.userListNextCursor = "next-page"; + const input = { requestId: randomUUID() }; + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toMatchObject({ status: "connection_required", nextCursor: "next-page" }); + f.state.userListNextCursor = ""; + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, { ...input, accountCursor: "next-page" })).toMatchObject({ status: "connection_required", nextCursor: null }); + expect(f.state.userListCursor).toBe("next-page"); + expect(posts(f)).toHaveLength(0); + }); + it("does not downgrade confirmed delivery when activity logging fails", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + const original = activityLogModule.logActivity; + const audit = vi.spyOn(activityLogModule, "logActivity").mockImplementation(async (database, entry) => { + if (entry.action === "chat_slack.test_message_sent") throw new Error("Synthetic activity failure"); + return original(database, entry); + }); + onTestFinished(() => audit.mockRestore()); + const input = { requestId: randomUUID() }; + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toMatchObject({ status: "sent" }); + expect(await f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).toMatchObject({ status: "sent" }); + expect(posts(f)).toHaveLength(1); + }); + it("does not restore revoked access or take another user's identity", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + await db.update(chatIdentityLinks).set({ status: "revoked" }).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID() })).rejects.toThrow("existing link"); + await db.update(chatIdentityLinks).set({ status: "linked", paperclipUserId: "another-user" }).where(eq(chatIdentityLinks.endpointId, f.endpoint.id)); + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID(), slackUserId: f.installerId })).rejects.toThrow("existing link"); + expect(posts(f)).toHaveLength(0); + }); + it("rejects wrong-workspace users, disabled DMs, and membership revocation", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + f.state.user.team_id = "TOTHER"; + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID() })).rejects.toThrow("personal Slack account"); + f.state.user.team_id = "TAUTO"; + await db.update(chatEndpoints).set({ allowDirectMessages: false }).where(eq(chatEndpoints.id, f.endpoint.id)); + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID() })).rejects.toThrow("Enable direct messages"); + await db.update(chatEndpoints).set({ allowDirectMessages: true }).where(eq(chatEndpoints.id, f.endpoint.id)); + await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, f.companyId), eq(companyMemberships.principalId, actor.userId))); + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, { requestId: randomUUID() })).rejects.toThrow("active Paperclip account"); + expect(posts(f)).toHaveLength(0); + }); + it("redacts provider failures and never retries an ambiguous send", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); f.state.timeoutWelcome = true; + const input = { requestId: randomUUID() }; + const result = await request(f.app).post(`/api/chat-endpoints/${f.endpoint.id}/slack/test-message`).send(input).expect(422); + expect(JSON.stringify(result.body)).not.toContain("canary"); + await expect(f.service.sendSlackTestMessage(f.endpoint.id, actor, input)).rejects.toThrow("may already have been sent"); + expect(posts(f)).toHaveLength(1); + const actions = await db.select().from(chatActions).where(and(eq(chatActions.endpointId, f.endpoint.id), eq(chatActions.kind, "slack_test_message"))); + expect(JSON.stringify(actions)).not.toContain("canary"); + expect(actions[0]!.status).toBe("uncertain"); + }); + }); + describe("managed Slack", () => { + it("keeps open-source setup independent of the broker", async () => { + const f = await fixture(); + expect(await f.service.slackRegistration.managerGrants.choices(f.companyId, actor)).toEqual({ managedAvailable: false, defaultMethod: "automatic", workspaces: [] }); + await f.create(); + expect((await f.service.get(f.endpoint.id)).setup.slackSetupMethod).toBe("automatic"); + await expect(f.service.update(f.endpoint.id, { slackSetupMethod: "managed" }, actor.userId)).rejects.toThrow(); + }); + it("keeps configured Cloud drafts managed when the broker profile disappears", async () => { + const f = await managedFixture(); + f.broker.getSlackManagerAppId.mockResolvedValue(null); + expect(f.grants.defaultMethod()).toBe("managed"); + expect(await f.grants.choices(f.companyId, actor)).toEqual({ managedAvailable: false, defaultMethod: "managed", workspaces: [] }); + const next = await f.service.create(f.companyId, { provider: "slack", assignedAgentId: f.assignedAgentId }, actor.userId); + expect(next.setup.slackSetupMethod).toBe("managed"); + await expect(f.provision()).rejects.toThrow(); + }); + it("creates and installs once, uploads the avatar and links only the manager grant owner", async () => { + const f = await managedFixture(); + await Promise.all([f.provision(), f.provision()]); + const saved = await f.service.get(f.endpoint.id); + expect(saved.setup).toMatchObject({ slackSetupMethod: "managed", slackRegistration: { status: "configured", appId: f.appId }, slackAvatar: { status: "uploaded" }, slackAccount: { status: "linked", paperclipUserId: actor.userId, externalUserId: f.installerId } }); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(1); + const activities = await db.select().from(activityLog).where(eq(activityLog.companyId, f.companyId)); + for (const secret of [f.credentials.accessToken, f.credentials.refreshToken!, f.token, clientSecret, signingSecret, "claim-canary"]) expect(JSON.stringify({ saved, activities, choices: await f.grants.choices(f.companyId, actor) })).not.toContain(secret); + await expect(f.service.update(f.endpoint.id, { slackSetupMethod: "automatic" }, actor.userId)).rejects.toThrow(); + }); + it("reuses the same personal workspace grant for another draft without another authorization", async () => { + const f = await managedFixture(); + const draft = await f.service.create(f.companyId, { provider: "slack", assignedAgentId: f.assignedAgentId, slackSetupMethod: "managed" }, actor.userId); + expect((await f.grants.choices(f.companyId, actor)).workspaces[0]!.grantId).toBe(f.grantId); + const otherActor = { ...actor, userId: "another-user", bypassPermissionCheck: true }; + expect((await f.grants.choices(f.companyId, otherActor)).workspaces).toEqual([]); + await expect(f.service.slackRegistration.managed.provision(draft.id, otherActor, { ...f.input, requestId: randomUUID() })).rejects.toThrow(); + expect(f.broker.startAuthorization).toHaveBeenCalledTimes(1); + }); + it("installs a second bot using the same grant without new manager consent", async () => { + const f = await managedFixture(); await f.provision(); + const secondBot = `${f.botId}SECOND`; + const secondApp = `${f.appId}SECOND`; + const scopes = buildSlackAppManifest({ app: appDetails, agentName: "Maya", webhookUrl: "https://paperclip.example/hook" }).oauth_config.scopes.bot; + const provider: typeof fetch = async (input, init) => { + const url = String(input); + if (url.endsWith("apps.manifest.create")) return Response.json({ ok: true, app_id: secondApp, team_id: "TAUTO", credentials: { client_id: "222.333", client_secret: clientSecret, signing_secret: signingSecret } }); + if (url.endsWith("apps.managedInstall")) return Response.json({ ok: true, app_id: secondApp, team_id: "TAUTO", api_access_tokens: { bot_access_token: "xoxb-second-managed" } }); + if (url.endsWith("auth.test")) return Response.json({ ok: true, team_id: "TAUTO", user_id: secondBot, bot_id: "BSECOND", team: "Test workspace" }, { headers: { "x-oauth-scopes": scopes.join(",") } }); + if (url.endsWith("bots.info")) return Response.json({ ok: true, bot: { id: "BSECOND", app_id: secondApp, user_id: secondBot, deleted: false } }); + return f.provider(input, init); + }; + const { service } = createService(new FakeChatSdkRuntime(), provider, { renderSlackAvatar: f.renderAvatar, slackManaged: { enabled: true, broker: f.broker as unknown as PaperclipCloudConnector } }); + const second = await service.create(f.companyId, { provider: "slack", assignedAgentId: f.assignedAgentId, slackSetupMethod: "managed", slackApp: appDetails }, actor.userId); + await service.slackRegistration.managed.provision(second.id, actor, { grantId: f.grantId, requestId: randomUUID() }); + expect((await service.get(second.id)).setup.slackRegistration).toMatchObject({ status: "configured", appId: secondApp }); + expect(f.broker.startAuthorization).toHaveBeenCalledTimes(1); + expect(f.broker.claim).toHaveBeenCalledTimes(1); + }); + it("rejects missing tool scopes before activating a managed bot", async () => { + const f = await managedFixture(); f.state.grantedScopes = []; + await f.provision(); + const saved = await f.service.get(f.endpoint.id); + expect(saved.providerAccountId).toBeNull(); + expect(saved.setup.slackRegistration).toMatchObject({ status: "credentials_saved", errorCode: "slack_install_scopes_missing" }); + }); + it.each(["revoked", "attention", "active"] as const)("reinstalls the configured app on an explicit %s reconnect", async status => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + await db.update(chatEndpoints).set({ status }).where(eq(chatEndpoints.id, f.endpoint.id)); + f.state.managedAccessToken = `xoxb-reconnected-${f.botId}`; f.state.rejectOldBotToken = true; + const reconnectInput = { ...f.input, requestId: randomUUID(), reconnect: true }; + await f.service.slackRegistration.managed.provision(f.endpoint.id, actor, reconnectInput); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(1); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(0); + expect((await f.service.get(f.endpoint.id)).status).not.toBe("revoked"); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration).toMatchObject({ status: "configured", appId: f.appId }); + await f.service.slackRegistration.managed.provision(f.endpoint.id, actor, reconnectInput); + await f.provision(); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(1); + }); + it("preserves configured-app reconnect intent when managed installation fails", async () => { + const f = await managedFixture(); await f.provision(); f.provider.mockClear(); + await db.update(chatEndpoints).set({ status: "revoked" }).where(eq(chatEndpoints.id, f.endpoint.id)); + f.state.managedError = "temporary_error"; + await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration).toMatchObject({ status: "install", appId: f.appId }); + f.state.managedError = null; + await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(0); + }); + it("reinstalls the saved app after missing bot scopes without creating another app", async () => { + const f = await managedFixture(); const scopes = f.state.grantedScopes; + f.state.grantedScopes = []; await f.provision(); + f.state.grantedScopes = scopes; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(2); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("retains reinstall intent after a provider failure instead of retrying rejected credentials", async () => { + const f = await managedFixture(); const scopes = f.state.grantedScopes; + f.state.grantedScopes = []; await f.provision(); + f.state.managedError = "internal_error"; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration).toMatchObject({ status: "install", errorCode: "slack_provider_failure" }); + f.state.managedError = null; f.state.grantedScopes = scopes; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(3); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("clears a manager reconnect error after renewing the bound grant", async () => { + const f = await managedFixture(); f.state.managedError = "invalid_auth"; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.errorCode).toBe("slack_manager_reauthorize"); + await f.grants.complete(await f.authorize(), "new-claim", null, actor); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.errorCode).toBeNull(); + f.state.managedError = null; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("reconnects a rejected manager token without losing pending manifest configuration", async () => { + const f = await managedFixture(); f.state.failManifestUpdate = true; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.errorCode).toBe("slack_manifest_update_pending"); + expect((await f.grants.choices(f.companyId, actor)).workspaces).toEqual([]); + expect((await f.grants.get(f.grantId, f.companyId, actor)).status).toBe("reauthorize"); + await f.grants.complete(await f.authorize(), "new-claim", null, actor); + f.state.failManifestUpdate = false; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect((await f.service.get(f.endpoint.id)).setup.slackAvatar?.status).toBe("uploaded"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.icon.set"))).toHaveLength(1); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("does not restore a revoked grant when an in-flight refresh returns", async () => { + const f = await managedFixture(); + await db.update(chatSlackManagerGrants).set({ expiresAt: new Date(0) }).where(eq(chatSlackManagerGrants.id, f.grantId)); + f.broker.refresh.mockImplementation(async () => { + await f.grants.revoke(f.grantId, f.companyId, actor); + return f.credentials; + }); + await expect(f.grants.token(f.grantId, f.companyId, actor)).rejects.toThrow(); + expect((await f.grants.get(f.grantId, f.companyId, actor)).status).toBe("revoked"); + }); + it("claims concurrent manager callbacks once", async () => { + const f = await managedFixture(); const state = await f.authorize(); + const results = await Promise.allSettled([1, 2].map(() => f.grants.complete(state, "claim", null, actor))); + expect(results.filter(result => result.status === "fulfilled")).toHaveLength(1); + expect(f.broker.claim).toHaveBeenCalledTimes(2); + }); + it("atomically consumes manager state and rejects wrong actors and expired attempts", async () => { + const f = await managedFixture(); + await expect(f.grants.complete(f.authorizeState, "replay", null, actor)).rejects.toThrow(); + const state = await f.authorize(); + await expect(f.grants.complete(state, "claim", null, { ...actor, userId: "another-user", bypassPermissionCheck: true })).rejects.toThrow(); + await db.update(toolOauthStates).set({ expiresAt: new Date(0) }).where(eq(toolOauthStates.state, state)); + await expect(f.grants.complete(state, "claim", null, actor)).rejects.toThrow(); + expect(f.broker.claim).toHaveBeenCalledTimes(1); + }); + it("preserves declined manager authorization as a safe recoverable outcome", async () => { + const f = await managedFixture(); + await f.grants.complete(await f.authorize(), null, "access_denied", actor); + expect((await f.service.get(f.endpoint.id)).setup.slackManagerError).toBe("slack_install_declined"); + }); + it("resumes saved installation credentials after inventory failure without creating or reinstalling", async () => { + const f = await managedFixture(); f.state.failInventory = true; + await f.provision(); + expect((await f.service.slackRegistration.registration(f.endpoint.id))?.status).toBe("credentials_saved"); + f.state.failInventory = false; await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("configured"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(1); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("never recreates an uncertain app", async () => { + const f = await managedFixture(); f.state.failCreate = true; + await f.provision(); await f.provision(); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.status).toBe("uncertain"); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.manifest.create"))).toHaveLength(1); + }); + it("uses bound OAuth for approval fallback and rejects a different installer", async () => { + const f = await managedFixture(); f.state.managedError = "app_approval_request_eligible"; + const result = await f.provision(); + const state = new URL(result.authorization!.authorizationUrl).searchParams.get("state")!; + f.state.oauth.authed_user.id = "UOTHER"; + await f.service.slackRegistration.complete(state, "code-canary", null, actor); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.errorCode).toBe("slack_install_identity_mismatch"); + expect((await f.service.get(f.endpoint.id)).providerAccountId).toBeNull(); + }); + it("does not activate a bot when the manager grant is revoked during child OAuth", async () => { + const f = await managedFixture(); f.state.managedError = "app_approval_request_eligible"; + const result = await f.provision(); + const state = new URL(result.authorization!.authorizationUrl).searchParams.get("state")!; + f.state.beforeExchange = () => f.grants.revoke(f.grantId, f.companyId, actor); + await f.service.slackRegistration.complete(state, "code-canary", null, actor); + const saved = await f.service.get(f.endpoint.id); + expect(saved.providerAccountId).toBeNull(); + expect(saved.setup.slackRegistration?.errorCode).toBe("slack_install_identity_mismatch"); + }); + it("uses the held transaction for grant queries without acquiring another pool connection", async () => { + const f = await managedFixture(); + const transactionContext = new AsyncLocalStorage(); + const originalTransaction = db.transaction.bind(db); + const transactionSpy = vi.spyOn(db, "transaction").mockImplementation((work, ...args) => + originalTransaction(tx => transactionContext.run(true, () => work(tx)), ...args)); + const querySpies = (["select", "insert", "update", "delete", "execute"] as const).map(method => { + const original = db[method].bind(db) as (...args: unknown[]) => unknown; + return vi.spyOn(db, method).mockImplementation(((...args: unknown[]) => { + if (transactionContext.getStore()) throw new Error("Acquired outer pool connection from held transaction"); + return original(...args); + }) as never); + }); + try { + await f.grants.token(f.grantId, f.companyId, actor); + await db.update(chatSlackManagerGrants).set({ expiresAt: new Date(0) }).where(eq(chatSlackManagerGrants.id, f.grantId)); + await f.grants.token(f.grantId, f.companyId, actor); + expect(f.broker.refresh).toHaveBeenCalledTimes(1); + await f.grants.request(f.grantId, f.companyId, actor, "apps.manifest.validate", { manifest: "{}" }); + await f.grants.complete(await f.authorize(), "renewed-claim", null, actor); + expect((await f.service.get(f.endpoint.id)).setup.slackManagerError).toBeUndefined(); + await f.grants.revoke(f.grantId, f.companyId, actor); + expect(f.broker.revoke).toHaveBeenCalledTimes(2); + } finally { + transactionSpy.mockRestore(); + for (const spy of querySpies) spy.mockRestore(); + } + }); + it("serializes refreshes across endpoints and never replays an ambiguous refresh", async () => { + const f = await managedFixture(); + await db.update(chatSlackManagerGrants).set({ expiresAt: new Date(0) }).where(eq(chatSlackManagerGrants.id, f.grantId)); + const tokens = await Promise.all([1, 2].map(() => f.grants.token(f.grantId, f.companyId, actor))); + expect(tokens.every(result => result.token === "xoxe.rotated-canary")).toBe(true); + expect(f.broker.refresh).toHaveBeenCalledTimes(1); + await db.update(chatSlackManagerGrants).set({ expiresAt: new Date(0) }).where(eq(chatSlackManagerGrants.id, f.grantId)); + f.broker.refresh.mockRejectedValue(new Error("provider echoed manager-canary")); + await expect(f.grants.token(f.grantId, f.companyId, actor)).rejects.toThrow(); + await expect(f.grants.token(f.grantId, f.companyId, actor)).rejects.toThrow(); + expect(f.broker.refresh).toHaveBeenCalledTimes(2); + }); + it("rejects a bot token belonging to a different Slack app before activation", async () => { + const f = await managedFixture(); f.state.managedBotAppId = "ADIFFERENT"; + await f.provision(); + const saved = await f.service.get(f.endpoint.id); + expect(saved.providerAccountId).toBeNull(); + expect(saved.setup.slackRegistration).toMatchObject({ status: "credentials_saved", errorCode: "slack_install_identity_mismatch" }); + }); + it("rejects manager identity changes and both callback-origin and ingress-origin drift", async () => { + const f = await managedFixture(); const state = await f.authorize(); + for (const change of [{ publicBaseUrl: "https://changed.example" }, { webhookPublicBaseUrl: "https://changed.example" }]) { + const changed = createService(new FakeChatSdkRuntime(), f.provider, { ...change, slackManaged: { enabled: true, broker: f.broker as unknown as PaperclipCloudConnector } }).service; + await expect(changed.slackRegistration.managerGrants.complete(state, "claim", null, actor)).rejects.toThrow(); + } + f.broker.getSlackManagerAppId.mockResolvedValue("ANEWMANAGER"); + await expect(f.provision()).rejects.toThrow(); + expect((await f.grants.choices(f.companyId, actor)).workspaces).toEqual([]); + }); + it("preserves provider rate-limit backoff across subsequent attempts", async () => { + const f = await managedFixture(); f.state.managedError = "ratelimited"; + await f.provision(); + f.state.managedError = null; await f.provision(); + expect(f.provider.mock.calls.filter(([url]) => String(url).endsWith("apps.managedInstall"))).toHaveLength(1); + expect((await f.service.get(f.endpoint.id)).setup.slackRegistration?.errorCode).toBe("slack_setup_rate_limited"); + }); + it("routes the broker claim_id callback back to the saved wizard without exposing the handoff", async () => { + const f = await managedFixture(); const state = await f.authorize(); + const response = await request(f.app).get(`/api/chat-slack/managed/oauth/callback?state=${state}&claim_id=sealed-claim-canary`).set("accept", "application/json").expect(303); + expect(f.broker.claim.mock.calls.at(-1)![0].claimId).toBe("sealed-claim-canary"); + expect(response.headers.location).toContain(f.endpoint.id); + expect(response.text).not.toContain("sealed-claim-canary"); + expect(response.headers["cache-control"]).toBe("no-store"); + }); + it("does not accept another company's grant or keep authorizations after removal", async () => { + const f = await managedFixture(); const other = await seedCompany(); + await expect(f.grants.token(f.grantId, other.companyId, { ...actor, bypassPermissionCheck: true })).rejects.toThrow(); + const state = await f.authorize(); + await f.service.configure(f.endpoint.id, { action: "remove" }, actor.userId); + await expect(f.grants.complete(state, "claim", null, actor)).rejects.toThrow(); + expect((await f.grants.get(f.grantId, f.companyId, actor)).status).toBe("active"); + }); + it("revocation invalidates pending authorization and prevents future provisioning", async () => { + const f = await managedFixture(); const state = await f.authorize(); + await f.grants.revoke(f.grantId, f.companyId, actor); + await expect(f.grants.complete(state, "claim", null, actor)).rejects.toThrow(); + await expect(f.provision()).rejects.toThrow(); + expect((await f.grants.choices(f.companyId, actor)).workspaces).toEqual([]); + }); + }); function setupEvent(f: Awaited>, patch: Record = {}, url = f.endpoint.setup.webhookUrl!, secret = signingSecret, timestamp?: string) { return signedSlackWebhookRequest({ url, contentType: "application/json", signingSecret: secret, ...(timestamp ? { timestamp } : {}), body: JSON.stringify({ type: "event_callback", api_app_id: f.appId, team_id: "TAUTO", event_id: "EvSETUP", event: { diff --git a/server/src/__tests__/http-log-policy.test.ts b/server/src/__tests__/http-log-policy.test.ts index f6a92125283..0c57d5159b0 100644 --- a/server/src/__tests__/http-log-policy.test.ts +++ b/server/src/__tests__/http-log-policy.test.ts @@ -65,7 +65,7 @@ describe("isPrivateWebhookHttpRequest", () => { describe("isSecretSensitiveHttpRequest", () => { it("protects Slack registration, installation, recovery, and all callback methods", () => { for (const suffix of ["registration", "install", "resume"]) expect(isSecretSensitiveHttpRequest("POST", `/api/chat-endpoints/id/slack/${suffix}`)).toBe(true); - for (const method of ["GET", "POST", "DELETE"]) for (const path of ["/api/chat-slack/oauth/callback?code=canary", "https://paperclip.test/API/CHAT-SLACK/OAUTH/callback?code=canary"]) + for (const method of ["GET", "POST", "DELETE"]) for (const path of ["/api/chat-slack/oauth/callback?code=canary", "/api/chat-slack/managed/oauth/callback?claim=canary", "/api/chat-slack/managed/oauth/callback?code=canary", "/api/chat-endpoints/endpoint/slack/managed/authorize", "/api/chat-endpoints/endpoint/slack/managed/provision", "https://paperclip.test/API/CHAT-SLACK/OAUTH/callback?code=canary"]) expect(isSecretSensitiveHttpRequest(method, path)).toBe(true); }); it("identifies credential-bearing chat setup mutations", () => { diff --git a/server/src/__tests__/redact-sensitive.test.ts b/server/src/__tests__/redact-sensitive.test.ts index b0467a7fb96..151c9212a5d 100644 --- a/server/src/__tests__/redact-sensitive.test.ts +++ b/server/src/__tests__/redact-sensitive.test.ts @@ -6,6 +6,11 @@ import { stripSecretBearingUrlParts, } from "../middleware/redact-sensitive.js"; +it("redacts Slack manager grants and sealed OAuth handoffs", () => { + const result = redactSensitive({ managerToken: "manager-canary", refreshToken: "refresh-canary", sealed: { ct: "sealed-canary" }, handoff: { token: "handoff-canary" }, claim_id: "claim-canary" }); + expect(JSON.stringify(result)).not.toContain("canary"); +}); + describe("redactSensitive", () => { it("redacts a plaintext password field on a sign-in body", () => { const body = { diff --git a/server/src/__tests__/slack-development-manager.test.ts b/server/src/__tests__/slack-development-manager.test.ts new file mode 100644 index 00000000000..314e087843c --- /dev/null +++ b/server/src/__tests__/slack-development-manager.test.ts @@ -0,0 +1,73 @@ +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { SLACK_MANAGER_SCOPES } from "@paperclipai/shared"; +import { developmentSlackManager } from "../services/connectors/slack/setup/development-manager.js"; + +const roots: string[] = []; +afterEach(() => roots.splice(0).forEach(path => rmSync(path, { recursive: true, force: true }))); +function fixture() { + const root = mkdtempSync(join(tmpdir(), "slack-development-")); roots.push(root); + const path = join(root, "manager.json"); + writeFileSync(path, JSON.stringify({ appId: "A123", clientId: "123.456", clientSecret: "client-secret-canary" }), { mode: 0o600 }); + return { path, env: { NODE_ENV: "development", PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED: "true", PAPERCLIP_SLACK_MANAGER_DEVELOPMENT_CONFIG: path } }; +} +const origin = () => "https://local-test.trycloudflare.com"; +const identity = { subject: "actor", companyId: "company", profile: "slack.manager" as const }; +const user = { id: "U123", token_type: "user", access_token: "access-canary", refresh_token: "refresh-canary", expires_in: 43200, scope: SLACK_MANAGER_SCOPES.join(",") }; +const response = (value: unknown) => new Response(JSON.stringify(value), { headers: { "content-type": "application/json" } }); + +describe("local managed Slack qualification", () => { + it("requires explicit development opt-in and refuses production", () => { + const { env } = fixture(); + expect(developmentSlackManager(origin, {})).toBeNull(); + expect(developmentSlackManager(origin, { ...env, NODE_ENV: "production" })).toBeNull(); + expect(developmentSlackManager(origin, { ...env, PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED: "false" })).toBeNull(); + }); + it("rejects readable secrets and invalid HTTPS origins", async () => { + const { env, path } = fixture(); chmodSync(path, 0o644); + expect(() => developmentSlackManager(origin, env)).toThrow(); + chmodSync(path, 0o600); + await expect(developmentSlackManager(() => "http://local.test", env)!.getSlackManagerAppId()).rejects.toThrow(); + }); + it("builds a scoped authorization URL without exposing the client secret", async () => { + const { env } = fixture(); + const client = developmentSlackManager(origin, env)!; + const result = await client.startAuthorization({ ...identity, returnUri: `${origin()}/api/chat-slack/managed/oauth/callback`, returnState: "bound-state" }); + const url = new URL(result.authorizationUrl); + expect(url.origin).toBe("https://slack.com"); + expect(url.searchParams.get("state")).toBe("bound-state"); + expect(url.searchParams.get("user_scope")).toBe(SLACK_MANAGER_SCOPES.join(",")); + expect(JSON.stringify(result)).not.toContain("client-secret-canary"); + await expect(client.startAuthorization({ ...identity, returnUri: "https://other.test/callback", returnState: "bound-state" })).rejects.toThrow(); + }); + it("exchanges the code against the exact callback and preserves actor/workspace identity", async () => { + const { env } = fixture(); + const fetcher = vi.fn().mockResolvedValue(response({ ok: true, app_id: "A123", team: { id: "T123", name: "Paperclip" }, authed_user: user })); + const result = await developmentSlackManager(origin, env, fetcher)!.claim({ ...identity, claimId: "code-canary", redemptionId: "once" }); + expect(result.slackManager).toEqual({ appId: "A123", workspaceId: "T123", workspaceName: "Paperclip", userId: "U123" }); + expect(result.subject).toBe("actor"); expect(result.companyId).toBe("company"); + const [url, request] = fetcher.mock.calls[0]; + expect(url).toBe("https://slack.com/api/oauth.v2.access"); + expect(request.redirect).toBe("error"); + expect(request.body.get("redirect_uri")).toBe(`${origin()}/api/chat-slack/managed/oauth/callback`); + expect(request.body.get("code")).toBe("code-canary"); + }); + it.each(["wrong-app", "missing-scope", "provider-echo"])("rejects %s without echoing secrets", async reason => { + const { env } = fixture(); + const value = reason === "provider-echo" ? { ok: false, error: "client-secret-canary code-canary" } : { ok: true, app_id: reason === "wrong-app" ? "AOTHER" : "A123", team: { id: "T123", name: "Paperclip" }, authed_user: { ...user, scope: reason === "missing-scope" ? "" : user.scope } }; + const client = developmentSlackManager(origin, env, vi.fn().mockResolvedValue(response(value)))!; + const error = await client.claim({ ...identity, claimId: "code-canary", redemptionId: "once" }).catch(error => error); + expect(error).toBeInstanceOf(Error); + expect(String(error)).not.toMatch(/canary/); + }); + it("rotates refresh credentials and verifies revocation", async () => { + const { env } = fixture(); + const fetcher = vi.fn().mockResolvedValueOnce(response({ ok: true, ...user })).mockResolvedValueOnce(response({ ok: false, error: "invalid_auth" })); + const client = developmentSlackManager(origin, env, fetcher)!; + expect((await client.refresh({ ...identity, refreshToken: "old-refresh" })).refreshToken).toBe("refresh-canary"); + expect(fetcher.mock.calls[0][1].body.get("grant_type")).toBe("refresh_token"); + await expect(client.revoke({ ...identity, token: "access-canary" })).rejects.toThrow(); + }); +}); diff --git a/server/src/middleware/http-log-policy.ts b/server/src/middleware/http-log-policy.ts index 5c5731659d2..222f97d476c 100644 --- a/server/src/middleware/http-log-policy.ts +++ b/server/src/middleware/http-log-policy.ts @@ -93,7 +93,7 @@ export function isSecretSensitiveHttpRequest( // Avatar uploads and dedicated Dot MCP calls may contain private image bytes. if (url && /^(?:\/mcp\/runner(?:\/|$)|\/api\/companies\/[^/]+\/agents\/[^/]+\/avatar(?:\/|$))/i.test(normalizePath(url).replace(/^https?:\/\/[^/]*/i, ""))) return true; if (!method || !url) return false; - if (/^\/api\/chat-slack\/oauth(?:\/|$)/i.test(normalizePath(url).replace(/^https?:\/\/[^/]*/i, ""))) return true; + if (/^\/api\/chat-slack\/(?:managed\/)?oauth(?:\/|$)/i.test(normalizePath(url).replace(/^https?:\/\/[^/]*/i, ""))) return true; if (/^\/api\/chat-endpoints\/[^/]+\/slack(?:\/|$)/i.test(normalizePath(url).replace(/^https?:\/\/[^/]*/i, ""))) return true; if (!SECRET_SENSITIVE_HTTP_METHODS.has(method.toUpperCase())) return false; const pathname = normalizePath(url); diff --git a/server/src/middleware/redact-sensitive.ts b/server/src/middleware/redact-sensitive.ts index 0b4ba2d39d4..3d6b874f6d7 100644 --- a/server/src/middleware/redact-sensitive.ts +++ b/server/src/middleware/redact-sensitive.ts @@ -16,6 +16,13 @@ const SENSITIVE_KEYS = new Set([ // material under `credentials`. Redact the whole subtree instead of trying // to keep an ever-changing allowlist of provider-specific field names in // sync with every connector. + "sealed", + "handoff", + "claim", + "claimid", + "claim_id", + "managertoken", + "manager_token", "configurationtoken", "configuration_token", "credential", diff --git a/server/src/routes/chat-channels.ts b/server/src/routes/chat-channels.ts index 6395e5ecd89..6dbde753c35 100644 --- a/server/src/routes/chat-channels.ts +++ b/server/src/routes/chat-channels.ts @@ -1,4 +1,4 @@ -import { slackRegistrationSchema, slackSetupActionSchema } from "@paperclipai/shared"; +import { slackTestMessageSchema, slackRegistrationSchema, slackSetupActionSchema, slackManagedProvisionSchema } from "@paperclipai/shared"; import { isCrossSiteOAuthCallbackNavigation, oauthCallbackInterstitialHtml } from "../lib/oauth-browser-return.js"; import { Router, @@ -164,6 +164,10 @@ export function chatChannelRoutes(db: Db, options: ChatChannelRouteOptions) { async (req, res) => { const companyId = req.params.companyId as string; await assertConnectionManager(req, companyId); + if (req.body.provider === "slack" && !req.body.slackSetupMethod) { + req.body.slackSetupMethod = service.slackRegistration.managerGrants.defaultMethod(); + } + if (req.body.slackSetupMethod === "managed" && service.slackRegistration.managerGrants.defaultMethod() !== "managed") throw forbidden("Managed Slack setup is unavailable"); res .status(201) .json(await service.create(companyId, req.body, actorUserId(req))); @@ -182,6 +186,50 @@ export function chatChannelRoutes(db: Db, options: ChatChannelRouteOptions) { return { userId: actor.actorId, sessionId: actor.sessionId, bypassPermissionCheck: req.actor.source === "local_implicit" || req.actor.isInstanceAdmin === true }; }; + router.get("/companies/:companyId/chat-slack/setup-options", async (req, res) => { + const companyId = req.params.companyId as string; + await assertConnectionManager(req, companyId); + res.set("Cache-Control", "no-store"); + res.json(await service.slackRegistration.managerGrants.choices(companyId, slackActor(req))); + }); + router.post("/chat-endpoints/:endpointId/slack/managed/authorize", validate(slackSetupActionSchema), async (req, res) => { + if (!(await assertEndpointManagementAccess(req, res))) return; + res.set("Cache-Control", "no-store"); + res.json(await service.slackRegistration.managerGrants.authorize(endpointId(req), slackActor(req))); + }); + router.post("/chat-endpoints/:endpointId/slack/managed/provision", validate(slackManagedProvisionSchema), async (req, res) => { + if (!(await assertEndpointManagementAccess(req, res))) return; + res.set("Cache-Control", "no-store"); + res.json(await service.slackRegistration.managed.provision(endpointId(req), slackActor(req), req.body)); + }); + router.delete("/companies/:companyId/chat-slack/manager-grants/:grantId", async (req, res) => { + const companyId = req.params.companyId as string; + await assertConnectionManager(req, companyId); + await service.slackRegistration.managerGrants.revoke(req.params.grantId as string, companyId, slackActor(req)); + res.sendStatus(204); + }); + router.get("/chat-slack/managed/oauth/callback", async (req, res) => { + res.set("Cache-Control", "no-store"); res.set("Referrer-Policy", "no-referrer"); + const actor = slackActor(req); + const state = typeof req.query.state === "string" ? req.query.state : ""; + const expiredId = await service.slackRegistration.managerGrants.expiredReturn(state, actor); + if (expiredId) { res.redirect(303, await service.slackRegistration.returnPath(expiredId)); return; } + const pending = await service.slackRegistration.managerGrants.pending(state, actor); + await assertConnectionManager(req, pending.current.endpoint.companyId); + if (req.get("accept")?.includes("text/html") && isCrossSiteOAuthCallbackNavigation(req)) { + res.type("html").send(oauthCallbackInterstitialHtml()); return; + } + const id = await service.slackRegistration.managerGrants.complete(state, + typeof req.query.claim_id === "string" ? req.query.claim_id : null, + typeof req.query.error === "string" ? req.query.error : null, actor, + typeof req.query.code === "string" ? req.query.code : null); + res.redirect(303, await service.slackRegistration.returnPath(id)); + }); + router.post("/chat-endpoints/:endpointId/slack/test-message", validate(slackTestMessageSchema), async (req, res) => { + if (!(await assertEndpointManagementAccess(req, res))) return; + res.set("Cache-Control", "no-store"); + res.json(await service.sendSlackTestMessage(endpointId(req), slackActor(req), req.body)); + }); router.post("/chat-endpoints/:endpointId/slack/registration", validate(slackRegistrationSchema), async (req, res) => { if (!(await assertEndpointManagementAccess(req, res))) return; res.set("Cache-Control", "no-store"); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index f39cd53431a..9b113c62805 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -1,5 +1,5 @@ import { updateFastResponseSchema } from "@paperclipai/shared"; -import { slackRegistrationSchema, slackSetupActionSchema, slackInstallAuthorizationSchema, slackRegistrationStateSchema, slackAppConfigurationSchema, slackAvatarStateSchema, slackAccountStateSchema } from "@paperclipai/shared"; +import { slackTestMessageSchema, slackTestMessageResultSchema, slackManagerAuthorizationSchema, slackSetupOptionsSchema, slackManagedProvisionSchema, slackManagedResultSchema, slackRegistrationSchema, slackSetupActionSchema, slackInstallAuthorizationSchema, slackRegistrationStateSchema, slackAppConfigurationSchema, slackAvatarStateSchema, slackAccountStateSchema } from "@paperclipai/shared"; import { experimentalApiMetadata } from "./experimental-api-metadata.js"; import { experimentalApiPaths, @@ -803,8 +803,9 @@ const chatAdapterCapabilitiesResponseSchema = z const chatEndpointSetupResponseSchema = z .object({ step: z.enum(["choose_agent", "provider_setup", "test", "complete"]), - slackSetupMethod: z.enum(["automatic", "manual", "existing"]).optional(), + slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(), slackRegistration: slackRegistrationStateSchema.optional(), + slackManagerError: z.string().optional(), slackAvatar: slackAvatarStateSchema.optional(), slackAccount: slackAccountStateSchema.optional(), slackOAuthCallbackUri: z.string().nullable().optional(), @@ -2563,6 +2564,41 @@ registry.registerPath({ }, }); +registry.registerPath({ + method: "get", path: "/api/companies/{companyId}/chat-slack/setup-options", tags: ["chat-channels"], + summary: "Discover Slack setup methods and the current user's authorized workspaces", + description: "Managed setup is advertised only for approved Cloud deployments with the broker profile enabled. Self-hosted discovery makes no hosted-service requests. Grants are personal and company scoped.", + request: { params: z.object({ companyId: z.string().uuid() }) }, responses: { 200: r.ok(slackSetupOptionsSchema), 401: r.unauthorized, 403: r.forbidden }, +}); +for (const action of ["authorize", "provision"] as const) registry.registerPath({ + method: "post", path: `/api/chat-endpoints/{endpointId}/slack/managed/${action}`, tags: ["chat-channels"], + summary: action === "authorize" ? "Authorize a personal Slack manager workspace grant" : "Provision the saved managed Slack app", + description: "Requires the selected managed method, company membership, connection-management permission and Cloud availability. Authorization state is actor/session bound and single use. Provisioning accepts a personal grant ID and idempotent request ID; no provider credentials or arbitrary manifests are accepted. Explicit reconnect uses reconnect=true with a fresh request ID to reinstall the saved app. Repeating that reconnect request returns its saved result without reinstalling a healthy connection.", + request: { params: z.object({ endpointId: z.string().uuid() }), body: jsonBody(action === "authorize" ? slackSetupActionSchema : slackManagedProvisionSchema) }, + responses: { 200: r.ok(action === "authorize" ? slackManagerAuthorizationSchema : slackManagedResultSchema), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 409: r.conflict, 422: r.unprocessable }, +}); +registry.registerPath({ + method: "delete", path: "/api/companies/{companyId}/chat-slack/manager-grants/{grantId}", tags: ["chat-channels"], + summary: "Revoke the current user's workspace grant for future managed provisioning", + description: "Removes local manager credentials and invalidates pending manager authorization. Installed bots remain connected; this does not uninstall Slack apps.", + request: { params: z.object({ companyId: z.string().uuid(), grantId: z.string().uuid() }) }, responses: { 204: { description: "Personal manager grant revoked" }, 401: r.unauthorized, 403: r.forbidden }, +}); +registry.registerPath({ + method: "get", path: "/api/chat-slack/managed/oauth/callback", tags: ["chat-channels"], + summary: "Redeem a sealed manager authorization and return to Slack setup", + description: "Authenticated, same-origin continuation with company, actor, session, endpoint revision and configured-origin checks. The broker handoff is single use; credentials are never returned to the browser. Explicit local development qualification accepts a Slack code instead of a broker claim; production ignores code.", + request: { query: z.object({ state: z.string(), claim_id: z.string().optional(), code: z.string().optional(), error: z.string().optional() }) }, + responses: { 200: { description: "Same-origin continuation page" }, 303: { description: "Return to saved setup" }, 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 409: r.conflict }, +}); + +registry.registerPath({ + method: "post", path: "/api/chat-endpoints/{endpointId}/slack/test-message", tags: ["chat-channels"], + summary: "Connect the current user's Slack identity and send a test DM", + description: "Requires a board connection manager and active company membership. Reuses only the actor's verified identity in the same workspace; otherwise returns paginated account choices and sends a private connection challenge. The account holder must confirm it while signed in before linking or test delivery. Revoked and conflicting links cannot be overwritten. The request ID prevents duplicate or ambiguous sends from being replayed.", + request: { params: z.object({ endpointId: z.string().uuid() }), body: jsonBody(slackTestMessageSchema) }, + responses: { 200: r.ok(slackTestMessageResultSchema), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 409: r.conflict, 422: r.unprocessable, 429: { description: "Wait before sending another test message" } }, +}); + for (const action of ["registration", "install", "resume"] as const) { registry.registerPath({ method: "post", path: `/api/chat-endpoints/{endpointId}/slack/${action}`, tags: ["chat-channels"], diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index 31338f7acc1..e1077280799 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -1,3 +1,6 @@ +import { slackTestMessageService } from "./connectors/slack/test-message.js"; +import type { SlackSetupOptions } from "./connectors/slack/setup/registration.js"; +import { isProvisionedSlackSetup } from "@paperclipai/shared"; import { createTeamsFileAuthority } from "./chat-channels/microsoft-teams/file-transfer-authority.js"; import { createPhotonEvents } from "./chat-channels/imessage-photon/events.js"; import { createTelegramPublicationDrafts } from "./chat-channels/telegram/publication-drafts.js"; @@ -1297,6 +1300,7 @@ export interface ChatChannelServiceOptions { claimId: string; }) => Promise; renderSlackAvatar?: (request: AgentAvatarRequest) => Promise; + slackManaged?: import("./connectors/slack/setup/registration.js").SlackSetupOptions["managed"]; storage?: StorageService; } @@ -1682,6 +1686,7 @@ function providerSetupState( slackApp: endpoint.setup.slackApp, slackSetupMethod: endpoint.setup.slackSetupMethod, slackAvatar: endpoint.setup.slackAvatar, + slackManagerError: endpoint.setup.slackManagerError, slackAccount: endpoint.setup.slackAccount, // Slack registers the slash command in the provider configuration. // Keep that identity immutable when the assigned agent is renamed; @@ -4414,7 +4419,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { actorUserId?: string | null, ) { if ((input.provider as string) === "agentmail") throw badRequest("Use the email inbox setup API for AgentMail"); - if (input.slackApp && input.provider !== "slack") throw unprocessable("Slack app details only apply to Slack connections"); + if ((input.slackApp || input.slackSetupMethod) && input.provider !== "slack") throw unprocessable("Slack app details only apply to Slack connections"); + if (input.slackSetupMethod === "managed" && slackRegistration.managerGrants.defaultMethod() !== "managed") throw conflict("Managed Slack setup is unavailable"); const agent = await db .select({ id: agents.id, name: agents.name, status: agents.status }) .from(agents) @@ -4526,7 +4532,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { step: "provider_setup", ...(input.provider === "slack" ? { slackApp: input.slackApp ?? defaultSlackAppConfiguration(agent.name), - command: (input.slackApp ?? defaultSlackAppConfiguration(agent.name)).command, slackSetupMethod: "automatic" } + command: (input.slackApp ?? defaultSlackAppConfiguration(agent.name)).command, slackSetupMethod: input.slackSetupMethod ?? slackRegistration.managerGrants.defaultMethod() } : {}), }, }); @@ -4572,13 +4578,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } values.communicationInstructions = input.communicationInstructions; } + if (input.slackSetupMethod === "managed" && slackRegistration.managerGrants.defaultMethod() !== "managed") throw conflict("Managed Slack setup is unavailable"); if (input.slackSetupMethod && (existing.endpoint.provider !== "slack" || existing.endpoint.botExternalId || existing.endpoint.status !== "draft")) { throw conflict("Setup method can only change before connecting the app"); } if (input.slackApp || input.slackSetupMethod) { const [registration] = await db.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, endpointId)); - if (registration && registration.status !== "failed") { - if (input.slackApp || input.slackSetupMethod === "automatic") throw conflict("Slack app details are locked after creation starts"); + if (registration && (registration.managerGrantId || registration.status !== "failed")) { + if (registration.managerGrantId || input.slackSetupMethod === "managed" || input.slackApp || input.slackSetupMethod === "automatic") throw conflict("Slack app details are locked after creation starts"); // Manual recovery fences pending authorization under the same credential lease. await slackRegistration.cleanup(endpointId, credentialLease); } @@ -6886,7 +6893,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { await syncSpekoVoiceTools(tx, endpoint, actorUserId, true, options.allowLocalVoiceBoard === true); } const observedSlackUrl = (current.setup as InternalSetupState).slackCallbackSurfaces?.events?.url; - const preserveSlackVerification = current.setup.slackSetupMethod === "automatic" + const preserveSlackVerification = isProvisionedSlackSetup(current.setup.slackSetupMethod) && (current.setup as InternalSetupState).slackVerificationSigningFingerprint === createHash("sha256").update(credentials.signingSecret ?? "").digest("hex") && Boolean(observedSlackUrl && slackCallbackMatchesPublicUrl(observedSlackUrl, `${getWebhookPublicBaseUrl()}/api/chat-webhooks/${endpoint.publicId}/slack`)); await notifyChatEndpointWork(tx); @@ -18637,7 +18644,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .where(and(eq(toolConnections.id, current.connectionId), eq(toolConnections.companyId, current.companyId))); if (!connection || connection.status === "archived") return new Response("ignored", { status: 200 }); let secret = (await resolveCredentialRefs(current, connection.refs.filter(ref => ref.configPath.replace(/^credentials\./, "") === "signingSecret"), tx)).signingSecret; - if (!secret && current.setup.slackSetupMethod === "automatic") { + if (!secret && isProvisionedSlackSetup(current.setup.slackSetupMethod)) { const [registration] = await tx.select().from(chatSlackRegistrations) .where(and(eq(chatSlackRegistrations.endpointId, current.id), eq(chatSlackRegistrations.companyId, current.companyId))); if (registration?.status !== "removed" && registration?.secretIds.signingSecret) { @@ -19252,7 +19259,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { // Recheck the signature under the current credential/runtime fence; // an SDK acknowledgement alone is not authentication evidence. let signingFingerprint: string | null = null; - if (!currentSetup.webhookVerifiedAt && currentSetup.slackSetupMethod === "automatic" + if (!currentSetup.webhookVerifiedAt && isProvisionedSlackSetup(currentSetup.slackSetupMethod) && callback.surface === "events" && !callback.isUrlVerification && slackCallbackMatchesPublicUrl(callback.url, `${getWebhookPublicBaseUrl()}/api/chat-webhooks/${current.publicId}/slack`)) { const payload = JSON.parse(eventBody) as { type?: string; team_id?: string; api_app_id?: string; event_id?: string; event?: { type?: string } }; @@ -26107,8 +26114,32 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return get(endpointId); } + const linkSlackAccount: SlackSetupOptions["linkInstaller"] = async (endpointId, user, actor, lease) => { + const record = await endpointRecord(endpointId); + if (!record || record.endpoint.provider !== "slack" || record.endpoint.providerAccountId !== user.team_id) throw conflict("Slack setup changed"); + const { principal } = await ensurePrincipal(record.endpoint, { + userId: String(user.id), userName: typeof user.name === "string" ? user.name : String(user.id), + fullName: typeof user.real_name === "string" ? user.real_name : String(user.id), isBot: false, isMe: false, + }); + await db.transaction(async tx => { + await lease.assertOwned(tx); + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`chat-identity:${record.endpoint.companyId}:${principal.id}`}, 0))`); + const membership = (await tx.select().from(companyMemberships).where(and(eq(companyMemberships.companyId, record.endpoint.companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.principalId, actor.userId))).for("update"))[0]; + if (membership?.status !== "active" || membership.membershipRole === "viewer") throw forbidden("The signed-in Paperclip account cannot use this connection"); + const links = await tx.select().from(chatIdentityLinks).where(and(eq(chatIdentityLinks.companyId, record.endpoint.companyId), eq(chatIdentityLinks.principalId, principal.id))).for("update"); + if (links.some(link => link.status === "linked" && link.paperclipUserId !== actor.userId) || links.some(link => link.endpointId === endpointId && link.status === "revoked")) throw conflict("This Slack account has an existing link", { code: "chat_identity_link_conflict" }); + if (links.some(link => link.endpointId === endpointId && link.status === "linked")) return; + await tx.insert(chatIdentityLinks).values({ companyId: record.endpoint.companyId, endpointId, principalId: principal.id, + paperclipUserId: actor.userId, status: "linked", confirmedAt: new Date() }) + .onConflictDoUpdate({ target: [chatIdentityLinks.endpointId, chatIdentityLinks.principalId], set: { + paperclipUserId: actor.userId, status: "linked", confirmationTokenHash: null, expiresAt: null, confirmedAt: new Date(), revokedAt: null, updatedAt: new Date(), + } }); + await lease.assertOwned(tx); + }); + }; + const slackRegistration = slackChatRegistrationService(db, { - publicOrigin: getPublicBaseUrl, webhookOrigin: getWebhookPublicBaseUrl, fetch: fetchImpl, renderAvatar: options.renderSlackAvatar, + publicOrigin: getPublicBaseUrl, webhookOrigin: getWebhookPublicBaseUrl, fetch: fetchImpl, renderAvatar: options.renderSlackAvatar, managed: options.slackManaged, withLock: async (endpointId, work) => { const record = await endpointRecord(endpointId); if (!record) throw notFound("Chat endpoint not found"); @@ -26136,29 +26167,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .where(and(eq(chatIdentityLinks.companyId, record.endpoint.companyId), eq(chatIdentityLinks.endpointId, endpointId), eq(chatIdentityLinks.status, "linked"), eq(chatIdentityLinks.paperclipUserId, account.paperclipUserId), eq(chatExternalPrincipals.externalId, account.externalUserId), eq(chatExternalPrincipals.providerAccountId, record.endpoint.providerAccountId!))); return Boolean(link); }, - linkInstaller: async (endpointId, user, actor, lease) => { - const record = await endpointRecord(endpointId); - if (!record || record.endpoint.provider !== "slack" || record.endpoint.providerAccountId !== user.team_id) throw conflict("Slack setup changed"); - const { principal } = await ensurePrincipal(record.endpoint, { - userId: String(user.id), userName: typeof user.name === "string" ? user.name : String(user.id), - fullName: typeof user.real_name === "string" ? user.real_name : String(user.id), isBot: false, isMe: false, - }); - await db.transaction(async tx => { - await lease.assertOwned(tx); - await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`chat-identity:${record.endpoint.companyId}:${principal.id}`}, 0))`); - const membership = (await tx.select().from(companyMemberships).where(and(eq(companyMemberships.companyId, record.endpoint.companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.principalId, actor.userId))).for("update"))[0]; - if (membership?.status !== "active" || membership.membershipRole === "viewer") throw forbidden("The signed-in Paperclip account cannot use this connection"); - const links = await tx.select().from(chatIdentityLinks).where(and(eq(chatIdentityLinks.companyId, record.endpoint.companyId), eq(chatIdentityLinks.principalId, principal.id))).for("update"); - if (links.some(link => link.status === "linked" && link.paperclipUserId !== actor.userId) || links.some(link => link.endpointId === endpointId && link.status === "revoked")) throw conflict("This Slack account has an existing link", { code: "chat_identity_link_conflict" }); - if (links.some(link => link.endpointId === endpointId && link.status === "linked")) return; - await tx.insert(chatIdentityLinks).values({ companyId: record.endpoint.companyId, endpointId, principalId: principal.id, - paperclipUserId: actor.userId, status: "linked", confirmedAt: new Date() }) - .onConflictDoUpdate({ target: [chatIdentityLinks.endpointId, chatIdentityLinks.principalId], set: { - paperclipUserId: actor.userId, status: "linked", confirmationTokenHash: null, expiresAt: null, confirmedAt: new Date(), revokedAt: null, updatedAt: new Date(), - } }); - await lease.assertOwned(tx); - }); - }, + linkInstaller: linkSlackAccount, configure: async (endpointId, credentials, actor, lease) => { const record = await endpointRecord(endpointId); if (!record) throw notFound("Chat endpoint not found"); @@ -26168,6 +26177,28 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }, }); + const sendSlackTestMessage = slackTestMessageService(db, { + fetch: fetchImpl, + withLock: async (endpointId, work) => { + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + return withCredentialMutationLease(record.endpoint, work); + }, + runtimeBotToken: async endpointId => { + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + return (await resolveCredentials(record.endpoint)).botToken; + }, + linkInstaller: linkSlackAccount, + ensureAccount: async (endpointId, user) => { + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + const { principal } = await ensurePrincipal(record.endpoint, { userId: String(user.id), userName: String(user.name ?? user.id), fullName: String(user.real_name ?? user.id), isBot: false, isMe: false }); + return principal.id; + }, + createLinkIntent, + }); + const githubRegistration = githubChatRegistrationService(db, { publicOrigin: () => getPublicBaseUrl(), webhookOrigin: () => getWebhookPublicBaseUrl(), fetch: fetchImpl, storeApp: async (endpointId, userId, app) => { await storeGitHubApp(endpointId, userId, { appId: app.appId, privateKey: app.privateKey, webhookSecret: app.webhookSecret, ...(app.clientId ? { clientId: app.clientId } : {}), ...(app.clientSecret ? { clientSecret: app.clientSecret } : {}) }, "manifest"); }, @@ -26345,6 +26376,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { voice, authorizeFastResponse, slackRegistration, + sendSlackTestMessage, saveGitHubSetupProgress: async (endpointId: string, stage: NonNullable["stage"]) => { const record = await endpointRecord(endpointId); if (!record || record.endpoint.provider !== "github") throw notFound("GitHub bot not found"); diff --git a/server/src/services/chat-slack-registration-cleanup.ts b/server/src/services/chat-slack-registration-cleanup.ts index 22a2b758598..97292a773a5 100644 --- a/server/src/services/chat-slack-registration-cleanup.ts +++ b/server/src/services/chat-slack-registration-cleanup.ts @@ -6,8 +6,11 @@ import type { CredentialMutationLeaseGuard } from "./chat-credential-mutation-le // Keep generic connection removal independent of the registration service's // authorization graph; access routes already load tool-access through the barrel. export async function removeSlackRegistration(db: Db, endpointId: string, lease: CredentialMutationLeaseGuard) { + await lease.assertOwned(); const vault = secretService(db); const [row] = await db.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, endpointId)); + const [endpoint] = await db.select().from(chatEndpoints).where(eq(chatEndpoints.id, endpointId)); + if (endpoint) await db.delete(toolOauthStates).where(and(eq(toolOauthStates.connectionId, endpoint.connectionId), like(toolOauthStates.state, "slack-manager.%"))); if (!row) return; await lease.assertOwned(); await db.update(chatSlackRegistrations).set({ status: "removed", updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); @@ -16,4 +19,4 @@ export async function removeSlackRegistration(db: Db, endpointId: string, lease: for (const id of Object.values(row.secretIds)) { await lease.assertOwned(); await vault.remove(id); } await lease.assertOwned(); await db.update(chatSlackRegistrations).set({ secretIds: {} }).where(eq(chatSlackRegistrations.endpointId, endpointId)); - } +} diff --git a/server/src/services/chat-slack-registration.ts b/server/src/services/chat-slack-registration.ts index 905c7ab0346..ea187cfff2f 100644 --- a/server/src/services/chat-slack-registration.ts +++ b/server/src/services/chat-slack-registration.ts @@ -1,531 +1,3 @@ -import { notifyChatVerificationWork } from "./chat-work-notifications.js"; -import { createAgentAvatarPool } from "./agent-avatar-pool.js"; -import type { AgentAvatarRequest } from "./agent-avatars.js"; -import { removeSlackRegistration } from "./chat-slack-registration-cleanup.js"; -import { createHash, randomBytes } from "node:crypto"; -import { and, eq, gt, inArray, like, sql } from "drizzle-orm"; -import { agents, chatEndpoints, chatSlackRegistrations, companies, toolConnections, toolOauthStates, type Db } from "@paperclipai/db"; -import { resolveAgentAppearance, buildSlackAppManifest, slackAppConfigurationSchema, slackRegistrationErrorMessage, type SlackRegistrationInput, type SlackRegistrationState, type SlackAvatarState, type SlackAccountState } from "@paperclipai/shared"; -import { badRequest, conflict, forbidden, notFound, unprocessable } from "../errors.js"; -import { accessService } from "./access.js"; -import { logActivity } from "./activity-log.js"; -import { logger } from "../middleware/logger.js"; -import { instanceSettingsService } from "./instance-settings.js"; -import { secretService } from "./secrets.js"; -import { writeConnectionCredential } from "./connection-credentials.js"; -import type { CredentialMutationLeaseGuard } from "./chat-credential-mutation-lease.js"; - -export interface SlackSetupActor { userId: string; sessionId: string | null; bypassPermissionCheck: boolean } -type Registration = typeof chatSlackRegistrations.$inferSelect; -const prefix = "slack-install."; -const timeoutMs = 20_000; -const object = (value: unknown): Record => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : {}; -const providerFailure = (code: string) => unprocessable(slackRegistrationErrorMessage(code), { code }); -const knownProviderErrors: Record = { - invalid_auth: "slack_configuration_token_invalid", token_expired: "slack_configuration_token_invalid", - token_revoked: "slack_configuration_token_invalid", not_authed: "slack_configuration_token_invalid", - invalid_manifest: "slack_manifest_invalid", invalid_app: "slack_manifest_invalid", - ratelimited: "slack_setup_rate_limited", no_permission: "slack_setup_permission_denied", - missing_scope: "slack_setup_permission_denied", not_allowed_token_type: "slack_configuration_token_invalid", -}; - -export function slackRegistrationProjection(row: Registration): SlackRegistrationState { - return { - status: row.status === "creating" && Date.now() - row.updatedAt.getTime() > 60_000 ? "uncertain" : row.status, - appId: row.appId, - managementUrl: row.appId ? `https://api.slack.com/apps/${encodeURIComponent(row.appId)}` : "https://api.slack.com/apps", - errorCode: row.status === "creating" && Date.now() - row.updatedAt.getTime() > 60_000 ? "slack_creation_uncertain" : row.errorCode, - }; -} - -export function slackChatRegistrationService(db: Db, options: { - publicOrigin: () => string | null; - webhookOrigin: () => string | null; - fetch?: typeof fetch; - renderAvatar?: (request: AgentAvatarRequest) => Promise; - withLock: (endpointId: string, work: (lease: CredentialMutationLeaseGuard) => Promise) => Promise; - runtimeSigningSecret: (endpointId: string) => Promise; - runtimeBotToken: (endpointId: string) => Promise; - canWelcome: (endpointId: string, account: SlackAccountState) => Promise; - linkInstaller: (endpointId: string, user: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) => Promise; - configure: (endpointId: string, credentials: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) => Promise; -}) { - const fetchImpl = options.fetch ?? fetch; - const vault = secretService(db); - let avatarPool: ReturnType | undefined; - function origin(value: string | null) { - if (!value) throw badRequest("Configure a public HTTPS URL before creating a Slack app"); - const url = new URL(value); - if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) - throw badRequest("Slack setup requires a configured public HTTPS origin"); - return url.origin; - } - const callbackUri = () => `${origin(options.publicOrigin())}/api/chat-slack/oauth/callback`; - async function registration(endpointId: string) { - return (await db.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, endpointId)))[0]; - } - async function endpoint(endpointId: string, actor: SlackSetupActor) { - if (!(await instanceSettingsService(db).getExperimental()).enableChatConnectors) throw forbidden("Chat connectors are disabled"); - const [row] = await db.select({ endpoint: chatEndpoints, connection: toolConnections, agentName: agents.name, agentAppearance: agents.appearance }) - .from(chatEndpoints).innerJoin(toolConnections, and(eq(toolConnections.id, chatEndpoints.connectionId), eq(toolConnections.companyId, chatEndpoints.companyId))) - .innerJoin(agents, and(eq(agents.id, chatEndpoints.assignedAgentId), eq(agents.companyId, chatEndpoints.companyId))) - .where(and(eq(chatEndpoints.id, endpointId), eq(chatEndpoints.provider, "slack"))); - if (!row || row.endpoint.status === "archived" || row.connection.status === "archived") throw notFound("Slack connection not found"); - if (!actor.bypassPermissionCheck && !await accessService(db).hasPermission(row.endpoint.companyId, "user", actor.userId, "tools:manage_connections")) - throw forbidden("Missing permission: tools:manage_connections"); - return row; - } - function assertOrigins(row: Registration, publicId: string) { - if (row.callbackUri !== callbackUri()) throw conflict("The Paperclip URL changed. Restore its configured address or update the Slack app and use manual setup."); - const expected = `${origin(options.webhookOrigin())}/api/chat-webhooks/${publicId}/slack`; - if (object(object(row.manifest.settings).event_subscriptions).request_url !== expected) - throw conflict("The Slack webhook address changed. Restore its configured address or update the Slack app and use manual setup."); - } - async function audit(row: Registration, actor: SlackSetupActor, action: string, code?: string) { - await logActivity(db, { companyId: row.companyId, actorType: "user", actorId: actor.userId, - action: `chat_slack.${action}`, entityType: "chat_endpoint", entityId: row.endpointId, - details: { endpointId: row.endpointId, appId: row.appId, ...(code ? { code } : {}) } }); - } - async function api(method: string, fields: Record | FormData, bearer?: string) { - const response = await fetchImpl(`https://slack.com/api/${method}`, { - method: "POST", redirect: "error", signal: AbortSignal.timeout(timeoutMs), - headers: { ...(fields instanceof FormData ? {} : { "content-type": "application/x-www-form-urlencoded" }), ...(bearer ? { authorization: `Bearer ${bearer}` } : {}) }, - body: fields instanceof FormData ? fields : new URLSearchParams(fields), - }); - const result = object(await response.json()); - if (!response.ok || result.ok !== true) { - const code = knownProviderErrors[String(result.error)] ?? (response.status === 429 ? "slack_setup_rate_limited" : "slack_provider_failure"); - throw providerFailure(code); - } - return result; - } - async function saveSecrets(row: Registration, values: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard, - patch: Partial = {}, account?: SlackAccountState) { - await db.transaction(async tx => { - await lease.assertOwned(tx); - const current = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)).for("update"))[0]; - if (!current || current.requestId !== row.requestId || current.status === "removed") throw conflict("Slack setup changed. Resume the saved connection."); - const ids = { ...current.secretIds }; - for (const [key, value] of Object.entries(values)) { - const result = await writeConnectionCredential(tx, { companyId: row.companyId, connectionName: "Slack app registration", - configPath: `slack_registration.${key}`, label: key, value, actor: { userId: actor.userId }, - ...(ids[key] ? { existingRef: { secretId: ids[key], configPath: `slack_registration.${key}` } } : {}) }); - ids[key] = result.secret.id; - } - await notifyChatVerificationWork(tx); - await tx.update(chatSlackRegistrations).set({ ...patch, secretIds: ids, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, row.endpointId)); - if (account) { - const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; - if (!saved || saved.status === "archived") throw conflict("Slack setup changed"); - // Reauthorization never changes an established personal account binding. - if (saved.setup.slackAccount?.status !== "linked") await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAccount: account }, updatedAt: new Date() }).where(eq(chatEndpoints.id, row.endpointId)); - } - await lease.assertOwned(tx); - }); - } - async function readSecret(row: Registration, key: string) { - if (!row.secretIds[key]) throw conflict("Slack setup credentials are unavailable. Resume the saved connection."); - return vault.resolveSecretValue(row.companyId, row.secretIds[key], "latest", { - accessContext: { consumerType: "system", consumerId: `slack-registration:${row.endpointId}`, configPath: `slack_registration.${key}`, actorType: "system" }, - }); - } - async function setFailure(row: Registration, status: Registration["status"], code: string, lease: CredentialMutationLeaseGuard) { - await lease.assertOwned(); - await db.update(chatSlackRegistrations).set({ status, errorCode: code, updatedAt: new Date() }) - .where(and(eq(chatSlackRegistrations.endpointId, row.endpointId), eq(chatSlackRegistrations.requestId, row.requestId))); - } - async function saveAvatar(row: Registration, avatar: SlackAvatarState, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { - await endpoint(row.endpointId, actor); - await db.transaction(async tx => { - await lease.assertOwned(tx); - const current = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)).for("update"))[0]; - if (!current || current.requestId !== row.requestId || current.status === "removed") throw conflict("Slack setup changed. Resume the saved connection."); - const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; - if (!saved || saved.status === "archived" || saved.setup.slackSetupMethod !== "automatic") throw conflict("Slack setup changed. Resume the saved connection."); - await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAvatar: avatar }, updatedAt: new Date() }) - .where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))); - await lease.assertOwned(tx); - }); - } - async function create(endpointId: string, actor: SlackSetupActor, input: SlackRegistrationInput) { - return options.withLock(endpointId, async lease => { - const current = await endpoint(endpointId, actor); - const previous = await registration(endpointId); - if (previous?.status === "removed") throw conflict("This registration was removed. Continue the selected manual setup."); - if (previous?.appId) { - if (previous.errorCode === "slack_manifest_update_pending") { - await configureManifest(previous, actor, input.credentials.configurationToken, lease); - } - return; - } - if (previous?.requestId === input.requestId) return; - if (previous && ["creating", "uncertain"].includes(previous.status) && !input.confirmedNoAppCreated) { - await setFailure(previous, "uncertain", "slack_creation_uncertain", lease); - return; - } - if (current.endpoint.status !== "draft" || current.endpoint.botExternalId || current.endpoint.setup.slackSetupMethod !== "automatic") - throw conflict("Automatic creation is only available for a new Slack connection"); - const app = slackAppConfigurationSchema.parse(current.endpoint.setup.slackApp); - const redirect = callbackUri(); - const manifest = buildSlackAppManifest({ app, agentName: current.agentName, - webhookUrl: `${origin(options.webhookOrigin())}/api/chat-webhooks/${current.endpoint.publicId}/slack`, redirectUri: redirect }); - // Slack cannot authenticate a challenge until its creation response gives us - // the signing secret. Add event subscriptions only after that secret is durable. - const { event_subscriptions: _events, ...creationSettings } = manifest.settings; - const creationManifest = { ...manifest, settings: creationSettings }; - try { await api("apps.manifest.validate", { manifest: JSON.stringify(manifest) }, input.credentials.configurationToken); } - catch (error) { - if (error instanceof Error && "status" in error) throw error; - throw providerFailure("slack_provider_failure"); - } - // No provider creation has happened until this intent is durable. - await endpoint(endpointId, actor); - await lease.assertOwned(); - const values = { companyId: current.endpoint.companyId, requestId: input.requestId, status: "creating" as const, - manifest, manifestHash: createHash("sha256").update(JSON.stringify(manifest)).digest("hex"), callbackUri: redirect, - createdByUserId: actor.userId, errorCode: null, updatedAt: new Date() }; - const [row] = await db.insert(chatSlackRegistrations).values({ endpointId, ...values }) - .onConflictDoUpdate({ target: chatSlackRegistrations.endpointId, set: values }).returning(); - await audit(row, actor, "creation_started"); - let createdAppId: string; - try { - const result = await api("apps.manifest.create", { manifest: JSON.stringify(creationManifest) }, input.credentials.configurationToken); - const credentials = object(result.credentials); - if (typeof result.app_id !== "string" || !/^A[A-Z0-9]+$/.test(result.app_id) - || typeof credentials.client_id !== "string" || !/^\d+\.\d+$/.test(credentials.client_id) - || typeof credentials.client_secret !== "string" || !credentials.client_secret - || typeof credentials.signing_secret !== "string" || !credentials.signing_secret) - throw new Error("Incomplete Slack creation response"); - await endpoint(endpointId, actor); - await saveSecrets(row, { signingSecret: credentials.signing_secret, clientSecret: credentials.client_secret }, actor, lease, - { appId: result.app_id, clientId: credentials.client_id, status: "install", errorCode: "slack_manifest_update_pending" }); - createdAppId = result.app_id; - } catch (error) { - // Only documented rejection responses prove that creation did not happen. - const code = object(object(error).details).code; - const rejected = typeof code === "string" && Object.values(knownProviderErrors).includes(code); - await setFailure(row, rejected ? "failed" : "uncertain", rejected ? code : "slack_creation_uncertain", lease); - await audit(row, actor, "creation_failed", rejected ? code : "slack_creation_uncertain"); - return; - } - // The app and credentials are durable before optional icon provisioning. - // A retry/restart always reuses this app, even if icon upload is interrupted. - const created = { ...row, appId: createdAppId }; - if (!await configureManifest(created, actor, input.credentials.configurationToken, lease)) return; - await saveAvatar(created, { status: "pending" }, actor, lease); - let avatar: SlackAvatarState; - try { - await endpoint(endpointId, actor); - await lease.assertOwned(); - // Cloud ingress requires a tenant session. Send the preset PNG bytes so - // Slack does not need to fetch an authenticated Paperclip URL. - const render = options.renderAvatar ?? (avatarPool ??= createAgentAvatarPool()).render; - const appearance = { ...resolveAgentAppearance(current.agentAppearance, current.endpoint.assignedAgentId) }; - delete appearance.customAvatarAssetId; // The preset renderer cannot read private uploaded assets. - const png = await render({ appearance, - size: 512, scale: 1, pose: "rest", muted: false, background: "paperclip-dark" }); - await endpoint(endpointId, actor); - await lease.assertOwned(); - const upload = new FormData(); - upload.set("app_id", createdAppId); - upload.set("file", new Blob([new Uint8Array(png)], { type: "image/png" }), "agent-avatar.png"); - await api("apps.icon.set", upload, input.credentials.configurationToken); - avatar = { status: "uploaded", uploadedAt: new Date().toISOString() }; - } catch { - // Provider echoes (including tokens) never become saved state or activity. - avatar = { status: "failed", errorCode: "slack_avatar_upload_failed" }; - } - await saveAvatar(created, avatar, actor, lease); - try { await audit(created, actor, avatar.status === "uploaded" ? "avatar_uploaded" : "avatar_upload_failed", avatar.status === "failed" ? avatar.errorCode : undefined); } - catch { logger.warn({ endpointId, appId: createdAppId }, "Slack avatar activity could not be recorded"); } - // An audit outage cannot change the saved creation outcome. - try { await audit({ ...row, appId: createdAppId }, actor, "app_created"); } - catch { logger.warn({ endpointId, appId: createdAppId }, "Slack app creation activity could not be recorded"); } - }); - } - async function configureManifest(row: Registration, actor: SlackSetupActor, configurationToken: string, lease: CredentialMutationLeaseGuard) { - const current = await endpoint(row.endpointId, actor); - if (current.endpoint.status !== "draft" || current.endpoint.setup.slackSetupMethod !== "automatic") - throw conflict("Resume the saved Slack connection before configuring its app"); - assertOrigins(row, current.endpoint.publicId); - await lease.assertOwned(); - try { - await api("apps.manifest.update", { app_id: row.appId!, manifest: JSON.stringify(row.manifest) }, configurationToken); - } catch { - // The pending marker was saved with the app secrets before dispatch. Retrying - // the same manifest is safe even when Slack's response was lost. - await audit(row, actor, "manifest_update_failed", "slack_manifest_update_pending"); - return false; - } - await endpoint(row.endpointId, actor); - await lease.assertOwned(); - await db.update(chatSlackRegistrations).set({ errorCode: null, updatedAt: new Date() }) - .where(and(eq(chatSlackRegistrations.endpointId, row.endpointId), eq(chatSlackRegistrations.requestId, row.requestId))); - await audit(row, actor, "manifest_updated"); - return true; - } - async function install(endpointId: string, actor: SlackSetupActor) { - return options.withLock(endpointId, async lease => { - const current = await endpoint(endpointId, actor); - const row = await registration(endpointId); - if (!row?.appId || !row.clientId || row.status === "removed") throw conflict("Create this Slack app before installing it"); - if (row.errorCode === "slack_manifest_update_pending") throw conflict("Finish configuring the saved Slack app before installing it"); - if (current.endpoint.setup.slackSetupMethod !== "automatic" || current.endpoint.status === "paused") throw conflict("Resume this connection before installing it"); - assertOrigins(row, current.endpoint.publicId); - const state = `${prefix}${randomBytes(32).toString("base64url")}`; - const expiresAt = new Date(Date.now() + 10 * 60_000); - const scopes = object(object(row.manifest.oauth_config).scopes).bot as string[]; - await db.transaction(async tx => { - await lease.assertOwned(tx); - await tx.delete(toolOauthStates).where(and(eq(toolOauthStates.connectionId, current.connection.id), like(toolOauthStates.state, `${prefix}%`))); - await tx.insert(toolOauthStates).values({ state, companyId: row.companyId, connectionId: current.connection.id, - codeVerifier: JSON.stringify({ endpointId, requestId: row.requestId, appId: row.appId, callbackUri: row.callbackUri, - endpointStatus: current.endpoint.status, runtimeGeneration: object(current.endpoint.setup).runtimeGeneration ?? 0 }), - createdByActorType: "user", createdByActorId: actor.userId, createdBySessionId: actor.sessionId, - requestedScopes: scopes, expiresAt }); - await tx.update(chatSlackRegistrations).set({ errorCode: null, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); - }); - const url = new URL("https://slack.com/oauth/v2/authorize"); - url.search = new URLSearchParams({ client_id: row.clientId, scope: scopes.join(","), state, redirect_uri: row.callbackUri, - ...(row.workspaceId ? { team: row.workspaceId } : {}) }).toString(); - await audit(row, actor, "installation_started"); - return { authorizationUrl: url.toString(), expiresAt: expiresAt.toISOString() }; - }); - } - async function expiredReturn(state: string, actor: SlackSetupActor) { - if (!/^slack-install\.[A-Za-z0-9_-]{43}$/.test(state)) return null; - const [attempt] = await db.select().from(toolOauthStates).where(eq(toolOauthStates.state, state)); - if (!attempt || attempt.expiresAt.getTime() > Date.now() || attempt.createdByActorId !== actor.userId || attempt.createdBySessionId !== actor.sessionId) return null; - const binding = object(JSON.parse(attempt.codeVerifier)); - const current = await endpoint(String(binding.endpointId), actor); - return options.withLock(current.endpoint.id, async lease => { - const latest = await endpoint(current.endpoint.id, actor); - const row = await registration(current.endpoint.id); - if (!row || row.status === "removed" || row.requestId !== binding.requestId || row.companyId !== attempt.companyId || latest.connection.id !== attempt.connectionId - || latest.endpoint.setup.slackSetupMethod !== "automatic" || row.appId !== binding.appId || row.callbackUri !== binding.callbackUri) return null; - assertOrigins(row, latest.endpoint.publicId); - await lease.assertOwned(); - await db.delete(toolOauthStates).where(eq(toolOauthStates.state, state)); - await setFailure(row, row.status, "slack_install_expired", lease); - await audit(row, actor, "installation_failed", "slack_install_expired"); - return returnPath(row.endpointId); - }); - } - async function pending(state: string, actor: SlackSetupActor) { - if (!/^slack-install\.[A-Za-z0-9_-]{43}$/.test(state)) throw badRequest("Invalid Slack authorization state"); - const [attempt] = await db.select().from(toolOauthStates).where(and(eq(toolOauthStates.state, state), gt(toolOauthStates.expiresAt, new Date()))); - if (!attempt || attempt.createdByActorId !== actor.userId || attempt.createdBySessionId !== actor.sessionId) - throw forbidden("Slack authorization expired or belongs to another session. Resume setup and install again."); - const binding = object(JSON.parse(attempt.codeVerifier)); - const current = await endpoint(String(binding.endpointId), actor); - const row = await registration(current.endpoint.id); - if (!row || row.status === "removed" || current.endpoint.setup.slackSetupMethod !== "automatic" - || current.endpoint.status === "paused" - || current.endpoint.status === "revoked" && binding.endpointStatus !== "revoked" - || binding.runtimeGeneration !== (object(current.endpoint.setup).runtimeGeneration ?? 0) - || row.requestId !== binding.requestId || row.appId !== binding.appId || row.callbackUri !== binding.callbackUri - || row.callbackUri !== callbackUri() || attempt.companyId !== row.companyId || attempt.connectionId !== current.connection.id) - throw conflict("Slack setup changed. Resume the saved connection and install again."); - assertOrigins(row, current.endpoint.publicId); - return { attempt, current, row }; - } - async function saveAccount(row: Registration, account: SlackAccountState, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { - await endpoint(row.endpointId, actor); - await db.transaction(async tx => { - await lease.assertOwned(tx); - const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; - const registrationNow = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)))[0]; - if (!saved || saved.status === "archived" || registrationNow?.status === "removed" || registrationNow?.requestId !== row.requestId) throw conflict("Slack setup changed"); - await notifyChatVerificationWork(tx); - await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAccount: account }, updatedAt: new Date() }).where(eq(chatEndpoints.id, row.endpointId)); - await lease.assertOwned(tx); - }); - } - async function linkAccount(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { - const current = await endpoint(row.endpointId, actor); - const account = current.endpoint.setup.slackAccount; - if (!account) throw providerFailure("slack_install_account_missing"); - if (account.status === "linked") return; - const token = row.secretIds.botToken ? await readSecret(row, "botToken") : await options.runtimeBotToken(row.endpointId); - const user = object((await api("users.info", { user: account.externalUserId }, token)).user); - if (user.id !== account.externalUserId || user.team_id !== row.workspaceId || user.is_bot !== false || user.is_app_user === true || user.deleted !== false) throw providerFailure("slack_install_account_missing"); - await endpoint(row.endpointId, actor); - await options.linkInstaller(row.endpointId, user, { ...actor, userId: account.paperclipUserId }, lease); - await saveAccount(row, { ...account, status: "linked" }, actor, lease); - try { await audit(row, actor, "account_linked"); } - catch { logger.warn({ endpointId: row.endpointId, appId: row.appId }, "Slack account activity could not be recorded"); } - } - async function sendSetupMessage(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard, kind: "welcome" | "verification") { - const current = await endpoint(row.endpointId, actor); - const account = current.endpoint.setup.slackAccount; - if (!account || account.status !== "linked") return; - if (kind === "verification" && (!current.endpoint.setup.webhookVerifiedAt || ["pending", "sending"].includes(account.welcomeStatus))) return; - const field = kind === "welcome" ? "welcomeStatus" : "verificationStatus"; - const status = account[field] ?? "pending"; - if (status === "sending") { - await saveAccount(row, { ...account, [field]: "uncertain" }, actor, lease); - return; // Never replay an ambiguous dispatch after a process restart. - } - if (status !== "pending") return; - await saveAccount(row, { ...account, [field]: "sending" }, actor, lease); - let result: SlackAccountState; - try { - await endpoint(row.endpointId, actor); - await lease.assertOwned(); - if (!await options.canWelcome(row.endpointId, account)) throw forbidden("Slack account access changed"); - const sent = await api("chat.postMessage", { channel: account.dmChannelId ?? account.externalUserId, - text: kind === "verification" ? "Your Slack connection is working! Now ask me a question about your Paperclip instance." - : `Hi, I’m ${current.agentName}. Your Slack account is connected to Paperclip.`, - unfurl_links: "false", unfurl_media: "false" }, await options.runtimeBotToken(row.endpointId)); - if (typeof sent.channel !== "string" || !/^D[A-Z0-9]+$/.test(sent.channel)) throw new Error("Incomplete message response"); - result = { ...account, [field]: "sent", dmChannelId: sent.channel }; - } catch (error) { - result = { ...account, [field]: error instanceof Error && "status" in error ? "failed" : "uncertain" }; - } - await saveAccount(row, result, actor, lease); - try { await audit(row, actor, `${kind}_${result[field] === "sent" ? "sent" : "failed"}`); } - catch { logger.warn({ endpointId: row.endpointId, appId: row.appId }, "Slack setup message activity could not be recorded"); } - } - async function welcome(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { - await sendSetupMessage(row, actor, lease, "welcome"); - await sendSetupMessage(row, actor, lease, "verification"); - } - async function notifyVerified(endpointId: string) { - return options.withLock(endpointId, async lease => { - const row = await registration(endpointId); - if (!row || row.status !== "configured") return; - const [saved] = await db.select({ setup: chatEndpoints.setup, status: chatEndpoints.status }).from(chatEndpoints).where(eq(chatEndpoints.id, endpointId)); - const account = saved?.setup.slackAccount; - if (!account || !["verifying", "active"].includes(saved.status)) return; - await sendSetupMessage(row, { userId: account.paperclipUserId, sessionId: null, bypassPermissionCheck: false }, lease, "verification"); - }); - } - function pendingVerificationMessage() { - // A pending verification cannot progress until linking, welcome, and an - // authenticated callback finish. Their commits wake this lane. - return and(eq(chatSlackRegistrations.status, "configured"), - inArray(chatEndpoints.status, ["verifying", "active"]), - sql`${chatEndpoints.setup}->'slackAccount'->>'status' = 'linked'`, - sql`${chatEndpoints.setup}->>'webhookVerifiedAt' is not null`, - sql`coalesce(${chatEndpoints.setup}->'slackAccount'->>'welcomeStatus', '') not in ('pending', 'sending')`, - sql`${chatEndpoints.setup}->'slackAccount'->>'verificationStatus' in ('pending', 'sending')`); - } - function pendingVerificationMessages(limit: number) { - return db.select({ endpointId: chatEndpoints.id }).from(chatEndpoints) - .innerJoin(chatSlackRegistrations, and(eq(chatSlackRegistrations.endpointId, chatEndpoints.id), eq(chatSlackRegistrations.companyId, chatEndpoints.companyId))) - .where(pendingVerificationMessage()).limit(limit); - } - async function nextVerificationMessageAt(): Promise { - return (await pendingVerificationMessages(1)).length ? Date.now() : null; - } - async function processPendingVerificationMessages(limit = 25) { - for (const row of await pendingVerificationMessages(limit)) { - try { await notifyVerified(row.endpointId); } - catch { logger.warn({ endpointId: row.endpointId }, "Slack verification message could not be processed"); } - } - } - async function resumeLocked(endpointId: string, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { - const current = await endpoint(endpointId, actor); - const row = await registration(endpointId); - if (!row || row.status === "removed") throw conflict("Slack registration is unavailable"); - if (row.status === "configured") { await cleanupStaged(endpointId, lease); await welcome(row, actor, lease); return; } - if (row.status !== "credentials_saved") throw conflict("Install this Slack app before connecting it"); - try { - assertOrigins(row, current.endpoint.publicId); - const botToken = await readSecret(row, "botToken"); - const auth = await api("auth.test", {}, botToken); - if (auth.team_id !== row.workspaceId || auth.user_id !== row.botUserId) throw providerFailure("slack_install_identity_mismatch"); - await options.configure(endpointId, { botToken, signingSecret: row.secretIds.signingSecret - ? await readSecret(row, "signingSecret") : await options.runtimeSigningSecret(endpointId) }, actor, { - assertOwned: async database => { - await lease.assertOwned(database); - await endpoint(endpointId, actor); - assertOrigins(row, current.endpoint.publicId); - }, - }); - await linkAccount(row, actor, lease); - } catch (error) { - const code = object(object(error).details).code; - const safeCode = code === "slack_install_account_missing" ? code : code === "chat_identity_link_conflict" || code === "chat_identity_already_linked" ? "slack_install_account_conflict" : code === "slack_install_identity_mismatch" || code === "chat_bot_identity_changed" - ? "slack_install_identity_mismatch" : code === "chat_provider_permissions_missing" ? "slack_install_scopes_missing" - : code === "chat_bot_identity_in_use" ? "slack_bot_already_connected" : "slack_configuration_incomplete"; - await setFailure(row, "credentials_saved", safeCode, lease); - await audit(row, actor, "configuration_failed", safeCode); - return; - } - await lease.assertOwned(); - await db.transaction(async tx => { - await notifyChatVerificationWork(tx); - await tx.update(chatSlackRegistrations).set({ status: "configured", errorCode: null, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); - }); - // Runtime now owns a separate vaulted copy. Keep the registration IDs until - // deletion succeeds so cleanup itself is retryable. - await cleanupStaged(endpointId, lease); - try { await audit(row, actor, "installation_completed"); } - catch { logger.warn({ endpointId, appId: row.appId }, "Slack installation activity could not be recorded"); } - await welcome(row, actor, lease); - } - async function cleanupStaged(endpointId: string, lease: CredentialMutationLeaseGuard) { - const row = await registration(endpointId); - if (!row || row.status !== "configured") return; - const ids = { ...row.secretIds }; - for (const key of ["botToken", "signingSecret"]) { - if (ids[key]) { await lease.assertOwned(); await vault.remove(ids[key]); delete ids[key]; } - } - await lease.assertOwned(); - await db.update(chatSlackRegistrations).set({ secretIds: ids }).where(eq(chatSlackRegistrations.endpointId, endpointId)); - } - async function complete(state: string, code: string | null, error: string | null, actor: SlackSetupActor) { - const before = await pending(state, actor); - return options.withLock(before.row.endpointId, async lease => { - const { row, attempt, current } = await pending(state, actor); - const [claimed] = await db.delete(toolOauthStates).where(and(eq(toolOauthStates.state, state), gt(toolOauthStates.expiresAt, new Date()))).returning(); - if (!claimed) throw conflict("This Slack authorization was already used"); - if (error) { - const failureCode = error === "access_denied" ? "slack_install_declined" : "slack_install_failed"; - await setFailure(row, row.status, failureCode, lease); - await audit(row, actor, "installation_failed", failureCode); - return row.endpointId; - } - try { - if (!code || code.length > 4096) throw providerFailure("slack_install_failed"); - const result = await api("oauth.v2.access", { code, client_id: row.clientId!, client_secret: await readSecret(row, "clientSecret"), redirect_uri: row.callbackUri }); - const teamId = object(result.team).id; - const installerId = object(result.authed_user).id; - if (result.app_id !== row.appId || result.token_type !== "bot" || typeof result.access_token !== "string" || !result.access_token.startsWith("xoxb-") - || typeof teamId !== "string" || !/^T[A-Z0-9]+$/.test(teamId) || typeof result.bot_user_id !== "string" || !/^[UW][A-Z0-9]+$/.test(result.bot_user_id) - || row.workspaceId && row.workspaceId !== teamId || row.botUserId && row.botUserId !== result.bot_user_id) - throw providerFailure("slack_install_identity_mismatch"); - const granted = new Set(String(result.scope ?? "").split(",")); - if ((attempt.requestedScopes ?? []).some(scope => !granted.has(scope))) throw providerFailure("slack_install_scopes_missing"); - if (current.endpoint.providerAccountId && current.endpoint.providerAccountId !== teamId - || current.endpoint.botExternalId && current.endpoint.botExternalId !== result.bot_user_id) - throw providerFailure("slack_install_identity_mismatch"); - if (typeof installerId !== "string" || !/^[UW][A-Z0-9]+$/.test(installerId) || installerId === result.bot_user_id) throw providerFailure("slack_install_account_missing"); - await endpoint(row.endpointId, actor); - // Reauthorization reuses the runtime signing secret after staging was cleaned. - await saveSecrets(row, { botToken: result.access_token }, actor, lease, - { status: "credentials_saved", workspaceId: teamId, botUserId: result.bot_user_id, errorCode: null }, - { externalUserId: installerId, paperclipUserId: actor.userId, status: "pending", welcomeStatus: "pending" }); - } catch (failure) { - const code = object(object(failure).details).code; - const safeCode = typeof code === "string" && code.startsWith("slack_install_") ? code : "slack_install_failed"; - await setFailure(row, row.status, safeCode, lease); - await audit(row, actor, "installation_failed", safeCode); - return row.endpointId; - } - await resumeLocked(row.endpointId, actor, lease); - return row.endpointId; - }); - } - const cleanup = (endpointId: string, lease: CredentialMutationLeaseGuard) => removeSlackRegistration(db, endpointId, lease); - async function returnPath(endpointId: string) { - const [row] = await db.select({ prefix: companies.issuePrefix }).from(chatEndpoints) - .innerJoin(companies, eq(companies.id, chatEndpoints.companyId)).where(eq(chatEndpoints.id, endpointId)); - if (!row) throw notFound("Slack connection not found"); - return `/${encodeURIComponent(row.prefix)}/apps/chat/connect?provider=slack&resume=${encodeURIComponent(endpointId)}`; - } - return { create, install, pending, expiredReturn, complete, cleanup, returnPath, registration, notifyVerified, processPendingVerificationMessages, nextVerificationMessageAt, - close: async () => { await avatarPool?.close(); }, - resume: (endpointId: string, actor: SlackSetupActor) => options.withLock(endpointId, lease => resumeLocked(endpointId, actor, lease)) }; -} +// Compatibility boundary: both provisioning paths converge on the same Slack runtime. +export { slackChatRegistrationService } from "./connectors/slack/setup/service.js"; +export { slackRegistrationProjection, type SlackSetupActor } from "./connectors/slack/setup/registration.js"; diff --git a/server/src/services/connectors/slack/setup/completion.ts b/server/src/services/connectors/slack/setup/completion.ts new file mode 100644 index 00000000000..afefa935789 --- /dev/null +++ b/server/src/services/connectors/slack/setup/completion.ts @@ -0,0 +1,164 @@ +import { notifyChatVerificationWork } from "../../../chat-work-notifications.js"; +import { and, eq, inArray, sql } from "drizzle-orm"; +import { chatEndpoints, chatSlackRegistrations } from "@paperclipai/db"; +import { SLACK_CHAT_BOT_SCOPES, SLACK_BOT_TOOL_SCOPES, type SlackAccountState } from "@paperclipai/shared"; +import { conflict, forbidden } from "../../../../errors.js"; +import { logger } from "../../../../middleware/logger.js"; +import { secretService } from "../../../secrets.js"; +import type { CredentialMutationLeaseGuard } from "../../../chat-credential-mutation-lease.js"; +import { object, providerFailure, type Registration, type SlackSetupActor, type SlackRegistrationStore } from "./registration.js"; +export function createSlackCompletion(store: SlackRegistrationStore) { + const { db, options, endpoint, registration, assertOrigins, api, readSecret, audit, setFailure } = store; + const vault = secretService(db); + async function saveAccount(row: Registration, account: SlackAccountState, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { + await endpoint(row.endpointId, actor); + await db.transaction(async tx => { + await lease.assertOwned(tx); + const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; + const registrationNow = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)))[0]; + if (!saved || saved.status === "archived" || registrationNow?.status === "removed" || registrationNow?.requestId !== row.requestId) throw conflict("Slack setup changed"); + await notifyChatVerificationWork(tx); + await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAccount: account }, updatedAt: new Date() }).where(eq(chatEndpoints.id, row.endpointId)); + await lease.assertOwned(tx); + }); + } + async function linkAccount(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { + const current = await endpoint(row.endpointId, actor); + const account = current.endpoint.setup.slackAccount; + if (!account) throw providerFailure("slack_install_account_missing"); + if (account.status === "linked") return; + const token = row.secretIds.botToken ? await readSecret(row, "botToken") : await options.runtimeBotToken(row.endpointId); + const user = object((await api("users.info", { user: account.externalUserId }, token)).user); + if (user.id !== account.externalUserId || user.team_id !== row.workspaceId || user.is_bot !== false || user.is_app_user === true || user.deleted !== false) throw providerFailure("slack_install_account_missing"); + await endpoint(row.endpointId, actor); + await options.linkInstaller(row.endpointId, user, { ...actor, userId: account.paperclipUserId }, lease); + await saveAccount(row, { ...account, status: "linked" }, actor, lease); + try { await audit(row, actor, "account_linked"); } + catch { logger.warn({ endpointId: row.endpointId, appId: row.appId }, "Slack account activity could not be recorded"); } + } + async function sendSetupMessage(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard, kind: "welcome" | "verification") { + const current = await endpoint(row.endpointId, actor); + const account = current.endpoint.setup.slackAccount; + if (!account || account.status !== "linked") return; + if (kind === "verification" && (!current.endpoint.setup.webhookVerifiedAt || ["pending", "sending"].includes(account.welcomeStatus))) return; + const field = kind === "welcome" ? "welcomeStatus" : "verificationStatus"; + const status = account[field] ?? "pending"; + if (status === "sending") { + await saveAccount(row, { ...account, [field]: "uncertain" }, actor, lease); + return; // Never replay an ambiguous dispatch after a process restart. + } + if (status !== "pending") return; + await saveAccount(row, { ...account, [field]: "sending" }, actor, lease); + let result: SlackAccountState; + try { + await endpoint(row.endpointId, actor); + await lease.assertOwned(); + if (!await options.canWelcome(row.endpointId, account)) throw forbidden("Slack account access changed"); + const sent = await api("chat.postMessage", { channel: account.dmChannelId ?? account.externalUserId, + text: kind === "verification" ? "Your Slack connection is working! Now ask me a question about your Paperclip instance." + : `Hi, I’m ${current.agentName}. Your Slack account is connected to Paperclip.`, + unfurl_links: "false", unfurl_media: "false" }, await options.runtimeBotToken(row.endpointId)); + if (typeof sent.channel !== "string" || !/^D[A-Z0-9]+$/.test(sent.channel)) throw new Error("Incomplete message response"); + result = { ...account, [field]: "sent", dmChannelId: sent.channel }; + } catch (error) { + result = { ...account, [field]: error instanceof Error && "status" in error ? "failed" : "uncertain" }; + } + await saveAccount(row, result, actor, lease); + try { await audit(row, actor, `${kind}_${result[field] === "sent" ? "sent" : "failed"}`); } + catch { logger.warn({ endpointId: row.endpointId, appId: row.appId }, "Slack setup message activity could not be recorded"); } + } + async function welcome(row: Registration, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { + await sendSetupMessage(row, actor, lease, "welcome"); + await sendSetupMessage(row, actor, lease, "verification"); + } + async function notifyVerified(endpointId: string) { + return options.withLock(endpointId, async lease => { + const row = await registration(endpointId); + if (!row || row.status !== "configured") return; + const [saved] = await db.select({ setup: chatEndpoints.setup, status: chatEndpoints.status }).from(chatEndpoints).where(eq(chatEndpoints.id, endpointId)); + const account = saved?.setup.slackAccount; + if (!account || !["verifying", "active"].includes(saved.status)) return; + await sendSetupMessage(row, { userId: account.paperclipUserId, sessionId: null, bypassPermissionCheck: false }, lease, "verification"); + }); + } + function pendingVerificationMessages(limit: number) { + return db.select({ endpointId: chatEndpoints.id }).from(chatEndpoints) + .innerJoin(chatSlackRegistrations, and(eq(chatSlackRegistrations.endpointId, chatEndpoints.id), eq(chatSlackRegistrations.companyId, chatEndpoints.companyId))) + .where(and(eq(chatSlackRegistrations.status, "configured"), inArray(chatEndpoints.status, ["verifying", "active"]), + sql`${chatEndpoints.setup}->'slackAccount'->>'status' = 'linked'`, + sql`${chatEndpoints.setup}->>'webhookVerifiedAt' is not null`, + sql`coalesce(${chatEndpoints.setup}->'slackAccount'->>'welcomeStatus', '') not in ('pending', 'sending')`, + sql`${chatEndpoints.setup}->'slackAccount'->>'verificationStatus' in ('pending', 'sending')`)).limit(limit); + } + async function nextVerificationMessageAt(): Promise { + return (await pendingVerificationMessages(1)).length ? Date.now() : null; + } + async function processPendingVerificationMessages(limit = 25) { + for (const row of await pendingVerificationMessages(limit)) { + try { await notifyVerified(row.endpointId); } + catch { logger.warn({ endpointId: row.endpointId }, "Slack verification message could not be processed"); } + } + } + async function resumeLocked(endpointId: string, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { + const current = await endpoint(endpointId, actor); + const row = await registration(endpointId); + if (!row || row.status === "removed") throw conflict("Slack registration is unavailable"); + if (row.status === "configured") { await cleanupStaged(endpointId, lease); await welcome(row, actor, lease); return; } + if (row.status !== "credentials_saved") throw conflict("Install this Slack app before connecting it"); + try { + assertOrigins(row, current.endpoint.publicId); + const botToken = await readSecret(row, "botToken"); + const auth = await api("auth.test", {}, botToken, row.managerGrantId ? [...SLACK_CHAT_BOT_SCOPES, ...SLACK_BOT_TOOL_SCOPES] : undefined); + if (auth.team_id !== row.workspaceId || row.botUserId && auth.user_id !== row.botUserId || typeof auth.user_id !== "string" || !/^[UW][A-Z0-9]+$/.test(auth.user_id)) throw providerFailure("slack_install_identity_mismatch"); + if (row.managerGrantId) { + if (typeof auth.bot_id !== "string" || !/^B[A-Z0-9]+$/.test(auth.bot_id)) throw providerFailure("slack_install_identity_mismatch"); + const bot = object((await api("bots.info", { bot: auth.bot_id }, botToken)).bot); + if (bot.id !== auth.bot_id || bot.app_id !== row.appId || bot.user_id !== auth.user_id || bot.deleted !== false) throw providerFailure("slack_install_identity_mismatch"); + } + if (!row.botUserId) { + if (!row.managerGrantId) throw providerFailure("slack_install_identity_mismatch"); + await lease.assertOwned(); + await db.update(chatSlackRegistrations).set({ botUserId: auth.user_id }).where(eq(chatSlackRegistrations.endpointId, endpointId)); + } + await options.configure(endpointId, { botToken, signingSecret: row.secretIds.signingSecret + ? await readSecret(row, "signingSecret") : await options.runtimeSigningSecret(endpointId) }, actor, { + assertOwned: async database => { + await lease.assertOwned(database); + await endpoint(endpointId, actor); + assertOrigins(row, current.endpoint.publicId); + }, + }); + await linkAccount(row, actor, lease); + } catch (error) { + const code = object(object(error).details).code; + const safeCode = code === "slack_configuration_token_invalid" ? "slack_install_token_invalid" : code === "slack_install_account_missing" ? code : code === "chat_identity_link_conflict" || code === "chat_identity_already_linked" ? "slack_install_account_conflict" : code === "slack_install_identity_mismatch" || code === "chat_bot_identity_changed" + ? "slack_install_identity_mismatch" : (code === "chat_provider_permissions_missing" || code === "slack_install_scopes_missing" || code === "slack_setup_permission_denied") ? "slack_install_scopes_missing" + : code === "chat_bot_identity_in_use" ? "slack_bot_already_connected" : "slack_configuration_incomplete"; + await setFailure(row, "credentials_saved", safeCode, lease); + await audit(row, actor, "configuration_failed", safeCode); + return; + } + await lease.assertOwned(); + await db.transaction(async tx => { + await notifyChatVerificationWork(tx); + await tx.update(chatSlackRegistrations).set({ status: "configured", errorCode: null, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); + }); + // Runtime now owns a separate vaulted copy. Keep the registration IDs until + // deletion succeeds so cleanup itself is retryable. + await cleanupStaged(endpointId, lease); + try { await audit(row, actor, "installation_completed"); } + catch { logger.warn({ endpointId, appId: row.appId }, "Slack installation activity could not be recorded"); } + await welcome(row, actor, lease); + } + async function cleanupStaged(endpointId: string, lease: CredentialMutationLeaseGuard) { + const row = await registration(endpointId); + if (!row || row.status !== "configured") return; + const ids = { ...row.secretIds }; + for (const key of ["botToken", "signingSecret"]) { + if (ids[key]) { await lease.assertOwned(); await vault.remove(ids[key]); delete ids[key]; } + } + await lease.assertOwned(); + await db.update(chatSlackRegistrations).set({ secretIds: ids }).where(eq(chatSlackRegistrations.endpointId, endpointId)); + } + return { resumeLocked, notifyVerified, processPendingVerificationMessages, nextVerificationMessageAt }; +} diff --git a/server/src/services/connectors/slack/setup/customer-owned.ts b/server/src/services/connectors/slack/setup/customer-owned.ts new file mode 100644 index 00000000000..6edd552d3fe --- /dev/null +++ b/server/src/services/connectors/slack/setup/customer-owned.ts @@ -0,0 +1,139 @@ +import { createHash } from "node:crypto"; +import { and, eq } from "drizzle-orm"; +import { chatSlackRegistrations } from "@paperclipai/db"; +import { resolveAgentAppearance, buildSlackAppManifest, slackAppConfigurationSchema, type SlackRegistrationInput, type SlackAvatarState } from "@paperclipai/shared"; +import { conflict } from "../../../../errors.js"; +import { logger } from "../../../../middleware/logger.js"; +import type { CredentialMutationLeaseGuard } from "../../../chat-credential-mutation-lease.js"; +import { object, knownProviderErrors, providerFailure, type Registration, type SlackSetupActor, type SlackRegistrationStore } from "./registration.js"; +export function createCustomerOwnedSetup(store: SlackRegistrationStore) { + const { db, options, endpoint, registration, callbackUri, origin, api, audit, saveSecrets, setFailure, saveAvatar, assertOrigins, renderAvatar } = store; + async function create(endpointId: string, actor: SlackSetupActor, input: SlackRegistrationInput, managed?: { grantId: string; workspaceId: string; request: typeof api }) { + return options.withLock(endpointId, async lease => { + const api = managed?.request ?? store.api; + const current = await endpoint(endpointId, actor); + const previous = await registration(endpointId); + const method = managed ? "managed" : "automatic"; + if (current.endpoint.setup.slackSetupMethod !== method) throw conflict("Resume the selected Slack setup method"); + if (previous && previous.managerGrantId !== (managed?.grantId ?? null)) throw conflict("Resume setup with the original Slack workspace"); + if (previous?.status === "removed") throw conflict("This registration was removed. Continue the selected manual setup."); + if (previous?.appId) { + if (previous.errorCode === "slack_manifest_update_pending") { + if (!await configureManifest(previous, actor, input.credentials.configurationToken, lease, method, api)) return; + if (current.endpoint.setup.slackAvatar?.status !== "uploaded") await configureAvatar(previous, actor, input.credentials.configurationToken, lease, api); + } + return; + } + if (previous?.requestId === input.requestId) return; + if (previous && ["creating", "uncertain"].includes(previous.status) && !input.confirmedNoAppCreated) { + await setFailure(previous, "uncertain", "slack_creation_uncertain", lease); + return; + } + if (current.endpoint.status !== "draft" || current.endpoint.botExternalId || current.endpoint.setup.slackSetupMethod !== method) + throw conflict("Automatic creation is only available for a new Slack connection"); + const app = slackAppConfigurationSchema.parse(current.endpoint.setup.slackApp); + const redirect = callbackUri(); + const manifest = buildSlackAppManifest({ app, agentName: current.agentName, + webhookUrl: `${origin(options.webhookOrigin())}/api/chat-webhooks/${current.endpoint.publicId}/slack`, redirectUri: redirect }); + // Slack cannot authenticate a challenge until its creation response gives us + // the signing secret. Add event subscriptions only after that secret is durable. + const { event_subscriptions: _events, ...creationSettings } = manifest.settings; + const creationManifest = { ...manifest, settings: creationSettings }; + try { await api("apps.manifest.validate", { manifest: JSON.stringify(manifest) }, input.credentials.configurationToken); } + catch (error) { + if (error instanceof Error && "status" in error) throw error; + throw providerFailure("slack_provider_failure"); + } + // No provider creation has happened until this intent is durable. + await endpoint(endpointId, actor); + await lease.assertOwned(); + const values = { companyId: current.endpoint.companyId, requestId: input.requestId, status: "creating" as const, + manifest, manifestHash: createHash("sha256").update(JSON.stringify(manifest)).digest("hex"), callbackUri: redirect, + createdByUserId: actor.userId, managerGrantId: managed?.grantId ?? null, workspaceId: managed?.workspaceId ?? null, errorCode: null, updatedAt: new Date() }; + const [row] = await db.insert(chatSlackRegistrations).values({ endpointId, ...values }) + .onConflictDoUpdate({ target: chatSlackRegistrations.endpointId, set: values }).returning(); + await audit(row, actor, "creation_started"); + let createdAppId: string; + try { + const result = await api("apps.manifest.create", { manifest: JSON.stringify(creationManifest) }, input.credentials.configurationToken); + const credentials = object(result.credentials); + if (typeof result.app_id !== "string" || !/^A[A-Z0-9]+$/.test(result.app_id) + || typeof credentials.client_id !== "string" || !/^\d+\.\d+$/.test(credentials.client_id) + || typeof credentials.client_secret !== "string" || !credentials.client_secret + || typeof credentials.signing_secret !== "string" || !credentials.signing_secret) + throw new Error("Incomplete Slack creation response"); + if (managed && result.team_id !== managed.workspaceId) throw new Error("Slack creation workspace mismatch"); + await endpoint(endpointId, actor); + await saveSecrets(row, { signingSecret: credentials.signing_secret, clientSecret: credentials.client_secret }, actor, lease, + { appId: result.app_id, clientId: credentials.client_id, status: "install", errorCode: "slack_manifest_update_pending" }); + createdAppId = result.app_id; + } catch (error) { + // Only documented rejection responses prove that creation did not happen. + const code = object(object(error).details).code; + const rejected = typeof code === "string" && [...Object.values(knownProviderErrors), "slack_manager_reauthorize"].includes(code); + await setFailure(row, rejected ? "failed" : "uncertain", rejected ? code : "slack_creation_uncertain", lease); + await audit(row, actor, "creation_failed", rejected ? code : "slack_creation_uncertain"); + return; + } + // The app and credentials are durable before optional icon provisioning. + // A retry/restart always reuses this app, even if icon upload is interrupted. + const created = { ...row, appId: createdAppId }; + if (!await configureManifest(created, actor, input.credentials.configurationToken, lease, method, api)) return; + await configureAvatar(created, actor, input.credentials.configurationToken, lease, api); + // An audit outage cannot change the saved creation outcome. + try { await audit({ ...row, appId: createdAppId }, actor, "app_created"); } + catch { logger.warn({ endpointId, appId: createdAppId }, "Slack app creation activity could not be recorded"); } + }); + } + async function configureAvatar(row: Registration, actor: SlackSetupActor, configurationToken: string, lease: CredentialMutationLeaseGuard, api = store.api) { + const current = await endpoint(row.endpointId, actor); + await saveAvatar(row, { status: "pending" }, actor, lease); + let avatar: SlackAvatarState; + try { + await endpoint(row.endpointId, actor); + await lease.assertOwned(); + // Cloud ingress requires a tenant session. Send the preset PNG bytes so + // Slack does not need to fetch an authenticated Paperclip URL. + const render = renderAvatar; + const appearance = { ...resolveAgentAppearance(current.agentAppearance, current.endpoint.assignedAgentId) }; + delete appearance.customAvatarAssetId; // The preset renderer cannot read private uploaded assets. + const png = await render({ appearance, + size: 512, scale: 1, pose: "rest", muted: false, background: "paperclip-dark" }); + await endpoint(row.endpointId, actor); + await lease.assertOwned(); + const upload = new FormData(); + upload.set("app_id", row.appId!); + upload.set("file", new Blob([new Uint8Array(png)], { type: "image/png" }), "agent-avatar.png"); + await api("apps.icon.set", upload, configurationToken); + avatar = { status: "uploaded", uploadedAt: new Date().toISOString() }; + } catch { + // Provider echoes (including tokens) never become saved state or activity. + avatar = { status: "failed", errorCode: "slack_avatar_upload_failed" }; + } + await saveAvatar(row, avatar, actor, lease); + try { await audit(row, actor, avatar.status === "uploaded" ? "avatar_uploaded" : "avatar_upload_failed", avatar.status === "failed" ? avatar.errorCode : undefined); } + catch { logger.warn({ endpointId: row.endpointId, appId: row.appId! }, "Slack avatar activity could not be recorded"); } + } + async function configureManifest(row: Registration, actor: SlackSetupActor, configurationToken: string, lease: CredentialMutationLeaseGuard, method: "automatic" | "managed" = "automatic", api = store.api) { + const current = await endpoint(row.endpointId, actor); + if (current.endpoint.status !== "draft" || current.endpoint.setup.slackSetupMethod !== method) + throw conflict("Resume the saved Slack connection before configuring its app"); + assertOrigins(row, current.endpoint.publicId); + await lease.assertOwned(); + try { + await api("apps.manifest.update", { app_id: row.appId!, manifest: JSON.stringify(row.manifest) }, configurationToken); + } catch { + // The pending marker was saved with the app secrets before dispatch. Retrying + // the same manifest is safe even when Slack's response was lost. + await audit(row, actor, "manifest_update_failed", "slack_manifest_update_pending"); + return false; + } + await endpoint(row.endpointId, actor); + await lease.assertOwned(); + await db.update(chatSlackRegistrations).set({ errorCode: null, updatedAt: new Date() }) + .where(and(eq(chatSlackRegistrations.endpointId, row.endpointId), eq(chatSlackRegistrations.requestId, row.requestId))); + await audit(row, actor, "manifest_updated"); + return true; + } + return { create, configureManifest }; +} diff --git a/server/src/services/connectors/slack/setup/development-manager.ts b/server/src/services/connectors/slack/setup/development-manager.ts new file mode 100644 index 00000000000..d4e71fd868a --- /dev/null +++ b/server/src/services/connectors/slack/setup/development-manager.ts @@ -0,0 +1,73 @@ +import { readFileSync, statSync } from "node:fs"; +import { isAbsolute } from "node:path"; +import { SLACK_MANAGER_SCOPES } from "@paperclipai/shared"; +import type { PaperclipCloudConnector, SealedConnectorCredentials } from "../../../paperclip-cloud-connector.js"; +import { createSlackSetupClient, object, providerFailure } from "./provider-client.js"; + +export type SlackManagerClient = Pick & { authorizationMode?: "code" }; + +// Explicit local qualification only. Production continues using the sealed Cloud +// broker; ordinary self-hosted installs neither load credentials nor contact it. +export function developmentSlackManager( + publicOrigin: () => string, + env: NodeJS.ProcessEnv = process.env, + fetchImpl: typeof fetch = fetch, +): SlackManagerClient | null { + if (env.NODE_ENV !== "development" || env.PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED !== "true" + || !env.PAPERCLIP_SLACK_MANAGER_DEVELOPMENT_CONFIG) return null; + let config: Record; + try { + const path = env.PAPERCLIP_SLACK_MANAGER_DEVELOPMENT_CONFIG; + if (!isAbsolute(path)) throw new Error(); + const stat = statSync(path); + if (!stat.isFile() || stat.size > 16_384 || (stat.mode & 0o077) !== 0) throw new Error(); + config = object(JSON.parse(readFileSync(path, "utf8"))); + } catch { throw providerFailure("slack_managed_unavailable"); } + const { appId, clientId, clientSecret } = config; + if (typeof appId !== "string" || !/^A[A-Z0-9]+$/.test(appId) + || typeof clientId !== "string" || !/^\d+\.\d+$/.test(clientId) + || typeof clientSecret !== "string" || !clientSecret) throw providerFailure("slack_managed_unavailable"); + const callback = () => { + const url = new URL(publicOrigin()); + if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw providerFailure("slack_managed_unavailable"); + return `${url.origin}/api/chat-slack/managed/oauth/callback`; + }; + const api = createSlackSetupClient(fetchImpl); + async function credentials(fields: Record, subject: string, companyId: string, initial: boolean): Promise { + let result: Record; + try { result = await api("oauth.v2.access", { client_id: clientId as string, client_secret: clientSecret as string, ...fields }); } + catch { throw providerFailure("slack_manager_reauthorize"); } + const user = initial ? object(result.authed_user) : result; + const scopes = typeof user.scope === "string" ? user.scope.split(/[, ]+/) : []; + const team = object(result.team); + if (typeof user.access_token !== "string" || !user.access_token || typeof user.refresh_token !== "string" || !user.refresh_token + || user.token_type !== "user" || typeof user.expires_in !== "number" || !Number.isFinite(user.expires_in) || user.expires_in <= 0 + || SLACK_MANAGER_SCOPES.some(scope => !scopes.includes(scope)) + || (initial && (result.app_id !== appId || typeof team.id !== "string" || !/^T[A-Z0-9]+$/.test(team.id) + || typeof team.name !== "string" || !team.name || typeof user.id !== "string" || !/^[UW][A-Z0-9]+$/.test(user.id)))) throw providerFailure("slack_manager_reauthorize"); + return { + v: 1, accessToken: user.access_token, refreshToken: user.refresh_token, tokenType: "user", + accessTokenExpiresAt: new Date(Date.now() + user.expires_in * 1000).toISOString(), refreshTokenExpiresAt: null, + scopes, subject, companyId, instanceId: "local-slack-qualification", environment: "development", provider: "slack", profile: "slack.manager", + ...(initial ? { slackManager: { appId: appId as string, workspaceId: team.id as string, workspaceName: team.name as string, userId: user.id as string } } : {}), + }; + } + return { + authorizationMode: "code", + async getSlackManagerAppId() { callback(); return appId as string; }, + async startAuthorization(values) { + if (values.returnUri !== callback() || values.profile !== "slack.manager") throw providerFailure("slack_manager_reauthorize"); + const url = new URL("https://slack.com/oauth/v2/authorize"); + url.search = new URLSearchParams({ client_id: clientId as string, user_scope: SLACK_MANAGER_SCOPES.join(","), redirect_uri: callback(), state: values.returnState }).toString(); + return { authorizationUrl: url.toString(), expiresAt: new Date(Date.now() + 600_000).toISOString() }; + }, + async claim(values) { return credentials({ code: values.claimId, redirect_uri: callback() }, values.subject, values.companyId, true); }, + async refresh(values) { return credentials({ grant_type: "refresh_token", refresh_token: values.refreshToken }, values.subject, values.companyId, false); }, + async revoke(values) { + try { await api("auth.revoke", {}, values.token); } + catch { throw providerFailure("slack_manager_reauthorize"); } + }, + }; +} diff --git a/server/src/services/connectors/slack/setup/managed.ts b/server/src/services/connectors/slack/setup/managed.ts new file mode 100644 index 00000000000..fc529fc7b9b --- /dev/null +++ b/server/src/services/connectors/slack/setup/managed.ts @@ -0,0 +1,81 @@ +import { and, eq } from "drizzle-orm"; +import { chatActions, chatSlackRegistrations } from "@paperclipai/db"; +import { type SlackManagedProvisionInput, type SlackManagedResult } from "@paperclipai/shared"; +import { conflict } from "../../../../errors.js"; +import type { createCustomerOwnedSetup } from "./customer-owned.js"; +import type { createSlackManagerGrants } from "./manager-grants.js"; +import type { createSlackCompletion } from "./completion.js"; +import { object, providerFailure, type SlackRegistrationStore, type SlackSetupActor } from "./registration.js"; + +/** Managed provisioning ends at the same saved-credential boundary as child OAuth. */ +export function createManagedSlackSetup(store: SlackRegistrationStore, grants: ReturnType, + provisioner: ReturnType, completion: ReturnType, + installFallback: (endpointId: string, actor: SlackSetupActor) => Promise<{ authorizationUrl: string; expiresAt: string }>) { + async function provision(endpointId: string, actor: SlackSetupActor, input: SlackManagedProvisionInput): Promise { + const current = await store.endpoint(endpointId, actor); + if (current.endpoint.setup.slackSetupMethod !== "managed") throw conflict("Resume the selected Slack setup method"); + const authorized = await grants.token(input.grantId, current.endpoint.companyId, actor); + await provisioner.create(endpointId, actor, { requestId: input.requestId, confirmedNoAppCreated: input.confirmedNoAppCreated, + credentials: { configurationToken: authorized.token } }, { grantId: authorized.grant.id, workspaceId: authorized.grant.workspaceId, request: (method, fields) => grants.request(input.grantId, current.endpoint.companyId, actor, method, fields) }); + let fallback = false; + await store.options.withLock(endpointId, async lease => { + const latest = await store.endpoint(endpointId, actor); + const row = await store.registration(endpointId); + if (!row?.appId || row.status === "removed" || row.errorCode === "slack_manifest_update_pending") return; + if (row.managerGrantId !== authorized.grant.id || latest.endpoint.setup.slackSetupMethod !== "managed") throw conflict("Slack setup changed"); + store.assertOrigins(row, latest.endpoint.publicId); + const reconnectKey = input.reconnect ? `slack_managed_reconnect:${actor.userId}:${input.requestId}` : null; + const [previous] = reconnectKey ? await store.db.select().from(chatActions).where(and(eq(chatActions.companyId, row.companyId), eq(chatActions.endpointId, endpointId), eq(chatActions.providerActionId, reconnectKey))) : []; + async function completeSaved() { + await completion.resumeLocked(endpointId, actor, lease); + if (reconnectKey && (await store.registration(endpointId))?.status === "configured") { + await lease.assertOwned(); + await store.db.update(chatActions).set({ status: "processed", updatedAt: new Date() }).where(and(eq(chatActions.companyId, row!.companyId), eq(chatActions.endpointId, endpointId), eq(chatActions.providerActionId, reconnectKey))); + } + } + if (previous && row.status === "configured" && !["revoked", "attention"].includes(latest.endpoint.status)) { + await completeSaved(); return; + } + const needsReinstall = row.status === "configured" && (input.reconnect || ["revoked", "attention"].includes(latest.endpoint.status)) + || row.status === "credentials_saved" && ["slack_install_token_invalid", "slack_install_scopes_missing"].includes(row.errorCode ?? ""); + if (reconnectKey && !previous) { + // Atomically bind an explicit reconnect to its durable installation intent. + // Duplicate requests can return the saved connection without reinstalling. + await store.db.transaction(async tx => { + await lease.assertOwned(tx); + await tx.insert(chatActions).values({ companyId: row.companyId, endpointId, kind: "slack_managed_reconnect", providerActionId: reconnectKey, status: "processing", payload: { grantId: row.managerGrantId, registrationRequestId: row.requestId } }); + if (needsReinstall) await tx.update(chatSlackRegistrations).set({ status: "install", errorCode: row.errorCode ?? "slack_install_failed", updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); + }); + } + if ((row.status === "configured" || row.status === "credentials_saved") && !needsReinstall) { + await completeSaved(); + return; + } + // Keep reinstall intent durable even if this dispatch fails or the process exits. + if (needsReinstall && (!reconnectKey || previous)) await store.setFailure(row, "install", row.errorCode ?? "slack_install_failed", lease); + const { grant } = await grants.token(input.grantId, row.companyId, actor); + try { + await lease.assertOwned(); + const result = await grants.request(grant.id, row.companyId, actor, "apps.managedInstall", { app_id: row.appId, team_id: grant.workspaceId }); + const botToken = object(result.api_access_tokens).bot_access_token; + if (result.app_id !== row.appId || result.team_id !== grant.workspaceId || typeof botToken !== "string" || !botToken.startsWith("xoxb-")) throw providerFailure("slack_install_identity_mismatch"); + if ((await grants.get(grant.id, row.companyId, actor)).status !== "active") throw providerFailure("slack_manager_reauthorize"); + await store.endpoint(endpointId, actor); + // Vault before auth.test/inventory so network failures never lose this installation. + await store.saveSecrets(row, { botToken }, actor, lease, { status: "credentials_saved", workspaceId: grant.workspaceId, errorCode: null }, + { externalUserId: grant.slackUserId, paperclipUserId: actor.userId, status: "pending", welcomeStatus: "pending" }); + } catch (error) { + const code = object(object(error).details).code; + const safe = typeof code === "string" ? code : "slack_install_failed"; + await store.setFailure(row, needsReinstall ? "install" : row.status, safe, lease); + await store.audit(row, actor, "installation_failed", safe); + fallback = ["slack_approval_required", "slack_approval_pending", "slack_approval_denied"].includes(safe); + return; + } + await completeSaved(); + }); + // OAuth's state/actor binding is ours; never trust a provider-supplied redirect. + return fallback ? { authorization: await installFallback(endpointId, actor) } : {}; + } + return { provision }; +} diff --git a/server/src/services/connectors/slack/setup/manager-grants.ts b/server/src/services/connectors/slack/setup/manager-grants.ts new file mode 100644 index 00000000000..3d902d9928e --- /dev/null +++ b/server/src/services/connectors/slack/setup/manager-grants.ts @@ -0,0 +1,269 @@ +import { randomBytes, randomUUID } from "node:crypto"; +import { and, eq, gt, like, sql } from "drizzle-orm"; +import { chatEndpoints, chatSlackManagerGrants, chatSlackRegistrations, companySecrets, toolOauthStates } from "@paperclipai/db"; +import { SLACK_MANAGER_SCOPES, type SlackSetupOptions as SetupChoices } from "@paperclipai/shared"; +import { conflict, forbidden } from "../../../../errors.js"; +import { getCloudRuntimeIdentity } from "../../../cloud-runtime-identity.js"; +import { createPaperclipCloudConnector, paperclipCloudConnectorConfigFromEnv, type SealedConnectorCredentials } from "../../../paperclip-cloud-connector.js"; +import { instanceSettingsService } from "../../../instance-settings.js"; +import { accessService } from "../../../access.js"; +import { secretService } from "../../../secrets.js"; +import { writeConnectionCredential } from "../../../connection-credentials.js"; +import { logActivity } from "../../../activity-log.js"; +import { object, providerFailure, type SlackRegistrationStore, type SlackSetupActor } from "./registration.js"; +import { developmentSlackManager, type SlackManagerClient } from "./development-manager.js"; + +export const SLACK_MANAGER_STATE_PREFIX = "slack-manager."; +export function createSlackManagerGrants(store: SlackRegistrationStore) { + const { db, options, endpoint, origin, api } = store; + type Database = Parameters[0]; + function broker(): SlackManagerClient | null { + if (options.managed) return options.managed.enabled ? options.managed.broker : null; + if (!getCloudRuntimeIdentity()) { + const development = developmentSlackManager(() => origin(options.publicOrigin())); + if (development) return development; + } + // Open-source instances never contact a hosted service to discover this path. + if (!getCloudRuntimeIdentity() || process.env.PAPERCLIP_SLACK_MANAGED_SETUP_ENABLED !== "true") return null; + const config = paperclipCloudConnectorConfigFromEnv(); + return config ? createPaperclipCloudConnector({ config }) : null; + } + async function access(companyId: string, actor: SlackSetupActor, database: Database = db) { + if (!(await instanceSettingsService(database as typeof db).getExperimental()).enableChatConnectors) throw forbidden("Chat connectors are disabled"); + if (!actor.bypassPermissionCheck && !await accessService(database as typeof db).hasPermission(companyId, "user", actor.userId, "tools:manage_connections")) throw forbidden("Missing permission: tools:manage_connections"); + } + // Deployment intent is stable across broker outages and preview rollbacks. + // A missing live profile pauses managed setup; it must not select token entry. + function defaultMethod(): "managed" | "automatic" { return broker() ? "managed" : "automatic"; } + async function available() { + const client = broker(); + if (!client || !(await instanceSettingsService(db).getExperimental()).enableChatConnectors) return false; + try { origin(options.publicOrigin()); origin(options.webhookOrigin()); return Boolean(await client.getSlackManagerAppId()); } + catch { return false; } + } + async function requiredBroker() { + if (!await available()) throw providerFailure("slack_managed_unavailable"); + return broker()!; + } + async function choices(companyId: string, actor: SlackSetupActor): Promise { + await access(companyId, actor); + const managedAvailable = await available(); + const managerAppId = managedAvailable ? await broker()!.getSlackManagerAppId() : null; + const grants = managedAvailable && managerAppId ? await db.select().from(chatSlackManagerGrants).where(and(eq(chatSlackManagerGrants.companyId, companyId), eq(chatSlackManagerGrants.userId, actor.userId), eq(chatSlackManagerGrants.status, "active"), eq(chatSlackManagerGrants.managerAppId, managerAppId))) : []; + return { managedAvailable, defaultMethod: defaultMethod(), workspaces: grants.map(g => ({ grantId: g.id, workspaceId: g.workspaceId, workspaceName: g.workspaceName, userId: g.slackUserId })) }; + } + const callbackUri = () => `${origin(options.publicOrigin())}/api/chat-slack/managed/oauth/callback`; + async function authorize(endpointId: string, actor: SlackSetupActor) { + const client = await requiredBroker(); + return options.withLock(endpointId, async lease => { + const current = await endpoint(endpointId, actor); + if (current.endpoint.setup.slackSetupMethod !== "managed") throw conflict("Resume the selected Slack setup method"); + const row = await store.registration(endpointId); + const revisions = await db.select({ id: chatSlackManagerGrants.id, revision: chatSlackManagerGrants.revision }).from(chatSlackManagerGrants) + .where(and(eq(chatSlackManagerGrants.companyId, current.endpoint.companyId), eq(chatSlackManagerGrants.userId, actor.userId))); + const state = `${SLACK_MANAGER_STATE_PREFIX}${randomBytes(32).toString("base64url")}`; + const expiresAt = new Date(Date.now() + 10 * 60_000); + await lease.assertOwned(); + await db.insert(toolOauthStates).values({ state, companyId: current.endpoint.companyId, connectionId: current.connection.id, + codeVerifier: JSON.stringify({ endpointId, revisions, authorizationMode: client.authorizationMode ?? "claim", requestId: row?.requestId ?? null, callbackUri: callbackUri(), webhookOrigin: origin(options.webhookOrigin()), runtimeGeneration: object(current.endpoint.setup).runtimeGeneration ?? 0 }), + createdByActorType: "user", createdByActorId: actor.userId, createdBySessionId: actor.sessionId, requestedScopes: [...SLACK_MANAGER_SCOPES], expiresAt }); + try { return await client.startAuthorization({ profile: "slack.manager", subject: actor.userId, companyId: current.endpoint.companyId, returnUri: callbackUri(), returnState: state }); } + catch { await db.delete(toolOauthStates).where(eq(toolOauthStates.state, state)); throw providerFailure("slack_manager_reauthorize"); } + }); + } + async function pending(state: string, actor: SlackSetupActor, allowExpired = false) { + if (!/^slack-manager\.[A-Za-z0-9_-]{43}$/.test(state)) throw forbidden("Invalid Slack authorization state"); + const [attempt] = await db.select().from(toolOauthStates).where(and(eq(toolOauthStates.state, state), ...(allowExpired ? [] : [gt(toolOauthStates.expiresAt, new Date())]))); + if (!attempt || attempt.createdByActorId !== actor.userId || attempt.createdBySessionId !== actor.sessionId) throw forbidden("Slack authorization expired or belongs to another session"); + const binding = object(JSON.parse(attempt.codeVerifier)); + const current = await endpoint(String(binding.endpointId), actor); + const row = await store.registration(current.endpoint.id); + if (current.endpoint.setup.slackSetupMethod !== "managed" || current.connection.id !== attempt.connectionId || current.endpoint.companyId !== attempt.companyId + || ["paused", "revoked"].includes(current.endpoint.status) || row?.status === "removed" + || (binding.authorizationMode ?? "claim") !== (broker()?.authorizationMode ?? "claim") + || (row?.requestId ?? null) !== binding.requestId || binding.callbackUri !== callbackUri() || binding.webhookOrigin !== origin(options.webhookOrigin()) + || binding.runtimeGeneration !== (object(current.endpoint.setup).runtimeGeneration ?? 0)) throw conflict("Slack setup changed. Resume authorization."); + return { attempt, current, row, binding }; + } + async function expiredReturn(state: string, actor: SlackSetupActor) { + const { current, attempt } = await pending(state, actor, true); + if (attempt.expiresAt.getTime() > Date.now()) return null; + return options.withLock(current.endpoint.id, async lease => { + const latest = await pending(state, actor, true); + await lease.assertOwned(); + await db.delete(toolOauthStates).where(eq(toolOauthStates.state, state)); + await db.update(chatEndpoints).set({ setup: { ...latest.current.endpoint.setup, slackManagerError: "slack_install_expired" } }).where(eq(chatEndpoints.id, current.endpoint.id)); + return current.endpoint.id; + }); + } + async function complete(state: string, claimId: string | null, error: string | null, actor: SlackSetupActor, authorizationCode: string | null = null) { + const client = await requiredBroker(); + claimId = client.authorizationMode === "code" ? authorizationCode : claimId; + const before = await pending(state, actor); + return options.withLock(before.current.endpoint.id, async lease => { + const { current, row, binding } = await pending(state, actor); + await lease.assertOwned(); + const [claimed] = await db.delete(toolOauthStates).where(and(eq(toolOauthStates.state, state), gt(toolOauthStates.expiresAt, new Date()))).returning(); + if (!claimed) throw conflict("Slack authorization was already used"); + async function outcome(code: string | null) { + await lease.assertOwned(); + const latest = await endpoint(current.endpoint.id, actor); + await db.update(chatEndpoints).set({ setup: { ...latest.endpoint.setup, slackManagerError: code ?? undefined } }).where(eq(chatEndpoints.id, current.endpoint.id)); + if (code) await logActivity(db, { companyId: current.endpoint.companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.manager_authorization_failed", entityType: "chat_endpoint", entityId: current.endpoint.id, details: { code } }); + return current.endpoint.id; + } + if (error || !claimId || claimId.length > 4096) return outcome(error === "access_denied" ? "slack_install_declined" : "slack_manager_reauthorize"); + try { + let grant: SealedConnectorCredentials; + try { grant = await client.claim({ profile: "slack.manager", subject: actor.userId, companyId: current.endpoint.companyId, claimId, redemptionId: randomUUID() }); } + catch { throw providerFailure("slack_manager_reauthorize"); } + const identity = grant.slackManager; + if (!identity || identity.appId !== await client.getSlackManagerAppId() || !/^A[A-Z0-9]+$/.test(identity.appId) || !/^T[A-Z0-9]+$/.test(identity.workspaceId) || !/^[UW][A-Z0-9]+$/.test(identity.userId) || typeof identity.workspaceName !== "string" || !identity.workspaceName || identity.workspaceName.length > 255) throw providerFailure("slack_manager_reauthorize"); + if (row?.managerGrantId) { + const existing = await get(row.managerGrantId, current.endpoint.companyId, actor); + if (existing.managerAppId !== identity.appId || existing.workspaceId !== identity.workspaceId || existing.slackUserId !== identity.userId) throw providerFailure("slack_install_identity_mismatch"); + } + await endpoint(current.endpoint.id, actor); + await access(current.endpoint.companyId, actor); + await db.transaction(async tx => { + await lease.assertOwned(tx); + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager:${current.endpoint.companyId}:${actor.userId}:${identity.appId}:${identity.workspaceId}`}, 0))`); + const [existing] = await tx.select().from(chatSlackManagerGrants).where(and(eq(chatSlackManagerGrants.companyId, current.endpoint.companyId), eq(chatSlackManagerGrants.userId, actor.userId), eq(chatSlackManagerGrants.managerAppId, identity.appId), eq(chatSlackManagerGrants.workspaceId, identity.workspaceId))); + if (existing) { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-refresh:${existing.id}`}, 0))`); + const [latest] = await tx.select().from(chatSlackManagerGrants).where(eq(chatSlackManagerGrants.id, existing.id)); + const revision = Array.isArray(binding.revisions) ? binding.revisions.map(object).find(value => value.id === existing.id)?.revision : undefined; + if (!latest || latest.revision !== revision) throw conflict("Workspace authorization changed. Start again."); + } + const [saved] = await tx.insert(chatSlackManagerGrants).values({ companyId: current.endpoint.companyId, userId: actor.userId, slackUserId: identity.userId, workspaceId: identity.workspaceId, workspaceName: identity.workspaceName, managerAppId: identity.appId }) + .onConflictDoUpdate({ target: [chatSlackManagerGrants.companyId, chatSlackManagerGrants.userId, chatSlackManagerGrants.managerAppId, chatSlackManagerGrants.workspaceId], set: { updatedAt: new Date() } }).returning(); + // A reauthorization must never change which Slack person this grant represents. + if (saved.slackUserId !== identity.userId) throw conflict("Reconnect using the original Slack account"); + await access(current.endpoint.companyId, actor, tx); + await save(tx, saved, grant, actor); + if (row?.managerGrantId === saved.id) { + await tx.update(chatSlackRegistrations).set({ errorCode: null, updatedAt: new Date() }) + .where(and(eq(chatSlackRegistrations.endpointId, current.endpoint.id), eq(chatSlackRegistrations.managerGrantId, saved.id), eq(chatSlackRegistrations.errorCode, "slack_manager_reauthorize"))); + } + }); + await logActivity(db, { companyId: current.endpoint.companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.manager_authorized", entityType: "chat_endpoint", entityId: current.endpoint.id, details: { workspaceId: identity.workspaceId, managerAppId: identity.appId } }); + return outcome(null); + } catch { return outcome("slack_manager_reauthorize"); } + }); + } + type Grant = typeof chatSlackManagerGrants.$inferSelect; + async function get(id: string, companyId: string, actor: SlackSetupActor) { + await access(companyId, actor); + return readGrant(db, id, companyId, actor); + } + async function readGrant(database: Database, id: string, companyId: string, actor: SlackSetupActor) { + await access(companyId, actor, database); + const [grant] = await database.select().from(chatSlackManagerGrants).where(and(eq(chatSlackManagerGrants.id, id), eq(chatSlackManagerGrants.companyId, companyId), eq(chatSlackManagerGrants.userId, actor.userId))); + if (!grant) throw forbidden("This Slack workspace authorization belongs to another account"); + return grant; + } + async function save(tx: Parameters[0]>[0], row: Grant, credentials: SealedConnectorCredentials, actor: SlackSetupActor) { + if (credentials.tokenType !== "user" || !credentials.accessToken || !credentials.refreshToken || !credentials.accessTokenExpiresAt || !Number.isFinite(Date.parse(credentials.accessTokenExpiresAt)) || Date.parse(credentials.accessTokenExpiresAt) <= Date.now() || credentials.provider !== "slack" || credentials.profile !== "slack.manager" || credentials.subject !== row.userId || credentials.companyId !== row.companyId || SLACK_MANAGER_SCOPES.some(scope => !credentials.scopes.includes(scope))) throw providerFailure("slack_manager_reauthorize"); + const write = (key: string, value: string, secretId: string | null) => writeConnectionCredential(tx, { companyId: row.companyId, ownerUserId: row.userId, actor: { userId: actor.userId }, connectionName: "Slack workspace management", configPath: `slack_manager.${key}`, definitionKey: `slack_manager.${row.id}.${key}`, label: key, value, ...(secretId ? { existingRef: { secretId, configPath: `slack_manager.${key}` } } : {}) }); + const accessToken = await write("access_token", credentials.accessToken, row.accessSecretId); + const refreshToken = credentials.refreshToken ? await write("refresh_token", credentials.refreshToken, row.refreshSecretId) : null; + await tx.update(chatSlackManagerGrants).set({ accessSecretId: accessToken.secret.id, refreshSecretId: refreshToken?.secret.id ?? row.refreshSecretId, expiresAt: credentials.accessTokenExpiresAt ? new Date(credentials.accessTokenExpiresAt) : null, status: "active", revision: row.revision + 1, updatedAt: new Date() }).where(eq(chatSlackManagerGrants.id, row.id)); + } + async function secret(row: Grant, id: string | null, database: Database = db) { + if (!id) throw providerFailure("slack_manager_reauthorize"); + const [ref] = await database.select().from(companySecrets).where(and(eq(companySecrets.id, id), eq(companySecrets.companyId, row.companyId), eq(companySecrets.scope, "user"), eq(companySecrets.ownerUserId, row.userId))); + if (!ref?.userSecretDefinitionId) throw providerFailure("slack_manager_reauthorize"); + // Owner-scoped system consumer; the durable grant is its authority, not a tool binding. + const value = await secretService(database).resolveUserSecretValue(row.companyId, { definitionId: ref.userSecretDefinitionId, responsibleUserId: row.userId }, { consumerType: "system", consumerId: `slack-manager:${row.id}`, actorType: "user", actorId: row.userId, responsibleUserId: row.userId }); + if (!value) throw providerFailure("slack_manager_reauthorize"); + return value.value; + } + const tokenRequests = new Map>(); + async function token(id: string, companyId: string, actor: SlackSetupActor) { + await access(companyId, actor); + const client = await requiredBroker(); + const managerAppId = await client.getSlackManagerAppId(); + const key = `${companyId}:${actor.userId}:${id}`; + const running = tokenRequests.get(key); + if (running) return running; + const operation = (async () => { + // Commit the single-use refresh claim before dispatch, without holding a + // pool connection while another query or the broker needs it. + const prepared = await db.transaction(async tx => { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-refresh:${id}`}, 0))`); + const grant = await readGrant(tx, id, companyId, actor); + if (grant.managerAppId !== managerAppId || grant.status !== "active") throw providerFailure("slack_manager_reauthorize"); + if (!grant.expiresAt || grant.expiresAt.getTime() > Date.now() + 60_000) return { grant, token: await secret(grant, grant.accessSecretId, tx) }; + const refreshToken = await secret(grant, grant.refreshSecretId, tx); + const [claimed] = await tx.update(chatSlackManagerGrants).set({ status: "reauthorize", revision: grant.revision + 1, updatedAt: new Date() }).where(eq(chatSlackManagerGrants.id, id)).returning(); + return { grant: claimed!, refreshToken }; + }); + if ("token" in prepared) return { grant: prepared.grant, token: prepared.token! }; + try { + const refreshed = await client.refresh({ profile: "slack.manager", subject: actor.userId, companyId, refreshToken: prepared.refreshToken }); + await access(companyId, actor); + return await db.transaction(async tx => { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-refresh:${id}`}, 0))`); + const current = await readGrant(tx, id, companyId, actor); + if (current.status !== "reauthorize" || current.revision !== prepared.grant.revision) throw providerFailure("slack_manager_reauthorize"); + await save(tx, current, refreshed, actor); + return { grant: await readGrant(tx, id, companyId, actor), token: refreshed.accessToken }; + }); + } catch { throw providerFailure("slack_manager_reauthorize"); } + })(); + tokenRequests.set(key, operation); + try { return await operation; } finally { if (tokenRequests.get(key) === operation) tokenRequests.delete(key); } + } + async function revoke(id: string, companyId: string, actor: SlackSetupActor) { + await access(companyId, actor); + let providerRevoked = true; + const grant = await db.transaction(async tx => { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-refresh:${id}`}, 0))`); + const current = await readGrant(tx, id, companyId, actor); + await tx.update(chatSlackManagerGrants).set({ status: "revoked", revision: current.revision + 1, updatedAt: new Date() }).where(eq(chatSlackManagerGrants.id, id)); + await tx.delete(toolOauthStates).where(and(eq(toolOauthStates.companyId, companyId), eq(toolOauthStates.createdByActorId, actor.userId), like(toolOauthStates.state, `${SLACK_MANAGER_STATE_PREFIX}%`))); + const client = broker(); + for (const secretId of [current.accessSecretId, current.refreshSecretId]) if (secretId) { + try { + if (!client) throw providerFailure("slack_managed_unavailable"); + await client.revoke({ subject: actor.userId, companyId, profile: "slack.manager", token: await secret(current, secretId, tx) }); + } catch { providerRevoked = false; } + await secretService(tx).remove(secretId); + } + await tx.update(chatSlackManagerGrants).set({ accessSecretId: null, refreshSecretId: null }).where(eq(chatSlackManagerGrants.id, id)); + return current; + }); + await logActivity(db, { companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.manager_revoked", entityType: "slack_manager_grant", entityId: id, details: { workspaceId: grant.workspaceId, providerRevoked } }); + } + async function request(id: string, companyId: string, actor: SlackSetupActor, method: string, fields: Record | FormData) { + const authorized = await token(id, companyId, actor); + const { grant } = authorized; + // Slack shares a method budget across manager tokens in the same workspace. + // Persist Retry-After across grants and process restarts; never retry a dispatch. + const result = await db.transaction(async tx => { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-api:${companyId}:${grant.managerAppId}:${grant.workspaceId}:${method}`}, 0))`); + // Serialize provider dispatch against refresh/revocation for this personal grant. + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`slack-manager-refresh:${id}`}, 0))`); + const peers = await tx.select().from(chatSlackManagerGrants).where(and(eq(chatSlackManagerGrants.companyId, companyId), eq(chatSlackManagerGrants.managerAppId, grant.managerAppId), eq(chatSlackManagerGrants.workspaceId, grant.workspaceId))).orderBy(chatSlackManagerGrants.id); + if (peers.some(peer => Date.parse(peer.rateLimits[method] ?? "") > Date.now())) return { error: providerFailure("slack_setup_rate_limited") }; + const current = await readGrant(tx, id, companyId, actor); + if (current.status !== "active") return { error: providerFailure("slack_manager_reauthorize") }; + try { return { value: await api(method, fields, await secret(current, current.accessSecretId, tx)) }; } + catch (error) { + const details = object(object(error).details); + if (details.code === "slack_setup_rate_limited") { + const until = new Date(Date.now() + Math.min(Number(details.retryAfterSeconds) || 60, 86_400) * 1000).toISOString(); + for (const peer of peers) await tx.update(chatSlackManagerGrants).set({ rateLimits: sql`${chatSlackManagerGrants.rateLimits} || ${JSON.stringify({ [method]: until })}::jsonb` }).where(eq(chatSlackManagerGrants.id, peer.id)); + } + if (details.code === "slack_configuration_token_invalid") { + await tx.update(chatSlackManagerGrants).set({ status: "reauthorize", revision: current.revision + 1, updatedAt: new Date() }).where(eq(chatSlackManagerGrants.id, id)); + return { error: providerFailure("slack_manager_reauthorize") }; + } + return { error }; + } + }); + if ("error" in result) throw result.error; + return result.value!; + } + return { choices, defaultMethod, available, authorize, pending, expiredReturn, complete, get, token, revoke, request }; +} diff --git a/server/src/services/connectors/slack/setup/provider-client.ts b/server/src/services/connectors/slack/setup/provider-client.ts new file mode 100644 index 00000000000..bc051d651cd --- /dev/null +++ b/server/src/services/connectors/slack/setup/provider-client.ts @@ -0,0 +1,58 @@ +import { slackRegistrationErrorMessage } from "@paperclipai/shared"; +import { unprocessable } from "../../../../errors.js"; +export const object = (value: unknown): Record => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : {}; +export const providerFailure = (code: string) => unprocessable(slackRegistrationErrorMessage(code), { code }); +export const knownProviderErrors: Record = { + app_approval_request_eligible: "slack_approval_required", app_approval_request_pending: "slack_approval_pending", + app_approval_request_denied: "slack_approval_denied", admin_approval_required: "slack_approval_required", + manager_app_not_eligible: "slack_managed_unavailable", feature_not_enabled: "slack_managed_unavailable", + invalid_auth: "slack_configuration_token_invalid", token_expired: "slack_configuration_token_invalid", + token_revoked: "slack_configuration_token_invalid", not_authed: "slack_configuration_token_invalid", + invalid_manifest: "slack_manifest_invalid", invalid_app: "slack_manifest_invalid", + ratelimited: "slack_setup_rate_limited", no_permission: "slack_setup_permission_denied", + missing_scope: "slack_setup_permission_denied", not_allowed_token_type: "slack_configuration_token_invalid", +}; + +const timeoutMs = 20_000; +export function createSlackSetupClient(fetchImpl: typeof fetch = fetch) { + return async function api(method: string, fields: Record | FormData, bearer?: string, requiredScopes?: readonly string[]) { + const response = await fetchImpl(`https://slack.com/api/${method}`, { + method: "POST", redirect: "error", signal: AbortSignal.timeout(timeoutMs), + headers: { ...(fields instanceof FormData ? {} : { "content-type": "application/x-www-form-urlencoded" }), ...(bearer ? { authorization: `Bearer ${bearer}` } : {}) }, + body: fields instanceof FormData ? fields : new URLSearchParams(fields), + }); + // Bound the streamed response as well as the request lifetime. + const reader = response.body?.getReader(); + const chunks: Uint8Array[] = []; + let size = 0; + if (reader) { + try { + while (true) { + const part = await reader.read(); + if (part.done) break; + size += part.value.byteLength; + if (size > 1_048_576) { await reader.cancel(); throw providerFailure("slack_provider_failure"); } + chunks.push(part.value); + } + } finally { reader.releaseLock(); } + } + let result: Record; + try { result = object(JSON.parse(Buffer.concat(chunks).toString("utf8"))); } + catch { result = {}; } + if (!response.ok || result.ok !== true) { + const code = knownProviderErrors[String(result.error)] ?? (response.status === 429 ? "slack_setup_rate_limited" : "slack_provider_failure"); + const retry = Number(response.headers.get("retry-after")); + if (response.status === 429 || result.error === "ratelimited") { + throw unprocessable(slackRegistrationErrorMessage("slack_setup_rate_limited"), { + code: "slack_setup_rate_limited", retryAfterSeconds: Number.isFinite(retry) && retry > 0 ? Math.ceil(retry) : 60, + }); + } + throw providerFailure(code); + } + if (requiredScopes) { + const granted = new Set((response.headers.get("x-oauth-scopes") ?? "").split(/[, ]+/)); + if (requiredScopes.some(scope => !granted.has(scope))) throw providerFailure("slack_install_scopes_missing"); + } + return result; + } +} diff --git a/server/src/services/connectors/slack/setup/registration.ts b/server/src/services/connectors/slack/setup/registration.ts new file mode 100644 index 00000000000..ca44f0e7f50 --- /dev/null +++ b/server/src/services/connectors/slack/setup/registration.ts @@ -0,0 +1,136 @@ +import { notifyChatVerificationWork } from "../../../chat-work-notifications.js"; +import { createAgentAvatarPool } from "../../../agent-avatar-pool.js"; +import type { AgentAvatarRequest } from "../../../agent-avatars.js"; +import { and, eq } from "drizzle-orm"; +import { agents, chatEndpoints, chatSlackRegistrations, toolConnections, type Db } from "@paperclipai/db"; +import { slackRegistrationErrorMessage, type SlackRegistrationState, type SlackAvatarState, type SlackAccountState, isProvisionedSlackSetup } from "@paperclipai/shared"; +import { badRequest, conflict, forbidden, notFound, unprocessable } from "../../../../errors.js"; +import { accessService } from "../../../access.js"; +import { logActivity } from "../../../activity-log.js"; +import { instanceSettingsService } from "../../../instance-settings.js"; +import { secretService } from "../../../secrets.js"; +import { writeConnectionCredential } from "../../../connection-credentials.js"; +import type { CredentialMutationLeaseGuard } from "../../../chat-credential-mutation-lease.js"; + +export interface SlackSetupActor { userId: string; sessionId: string | null; bypassPermissionCheck: boolean } +export type Registration = typeof chatSlackRegistrations.$inferSelect; + + +export { object, providerFailure, knownProviderErrors } from "./provider-client.js"; +import { object } from "./provider-client.js"; + +export function slackRegistrationProjection(row: Registration): SlackRegistrationState { + return { + status: row.status === "creating" && Date.now() - row.updatedAt.getTime() > 60_000 ? "uncertain" : row.status, + appId: row.appId, + ...(row.managerGrantId ? { managerGrantId: row.managerGrantId } : {}), + managementUrl: row.appId ? `https://api.slack.com/apps/${encodeURIComponent(row.appId)}` : "https://api.slack.com/apps", + errorCode: row.status === "creating" && Date.now() - row.updatedAt.getTime() > 60_000 ? "slack_creation_uncertain" : row.errorCode, + }; +} + +import { createSlackSetupClient } from "./provider-client.js"; +export type SlackSetupOptions = { + managed?: { enabled: boolean; broker: import("../../../paperclip-cloud-connector.js").PaperclipCloudConnector }; + publicOrigin: () => string | null; + webhookOrigin: () => string | null; + fetch?: typeof fetch; + renderAvatar?: (request: AgentAvatarRequest) => Promise; + withLock: (endpointId: string, work: (lease: CredentialMutationLeaseGuard) => Promise) => Promise; + runtimeSigningSecret: (endpointId: string) => Promise; + runtimeBotToken: (endpointId: string) => Promise; + canWelcome: (endpointId: string, account: SlackAccountState) => Promise; + linkInstaller: (endpointId: string, user: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) => Promise; + configure: (endpointId: string, credentials: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) => Promise; +}; +export function createSlackRegistrationStore(db: Db, options: SlackSetupOptions) { + const fetchImpl = options.fetch ?? fetch; + const vault = secretService(db); + let avatarPool: ReturnType | undefined; + function origin(value: string | null) { + if (!value) throw badRequest("Configure a public HTTPS URL before creating a Slack app"); + const url = new URL(value); + if (url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash) + throw badRequest("Slack setup requires a configured public HTTPS origin"); + return url.origin; + } + const callbackUri = () => `${origin(options.publicOrigin())}/api/chat-slack/oauth/callback`; + async function registration(endpointId: string) { + return (await db.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, endpointId)))[0]; + } + async function endpoint(endpointId: string, actor: SlackSetupActor) { + if (!(await instanceSettingsService(db).getExperimental()).enableChatConnectors) throw forbidden("Chat connectors are disabled"); + const [row] = await db.select({ endpoint: chatEndpoints, connection: toolConnections, agentName: agents.name, agentAppearance: agents.appearance }) + .from(chatEndpoints).innerJoin(toolConnections, and(eq(toolConnections.id, chatEndpoints.connectionId), eq(toolConnections.companyId, chatEndpoints.companyId))) + .innerJoin(agents, and(eq(agents.id, chatEndpoints.assignedAgentId), eq(agents.companyId, chatEndpoints.companyId))) + .where(and(eq(chatEndpoints.id, endpointId), eq(chatEndpoints.provider, "slack"))); + if (!row || row.endpoint.status === "archived" || row.connection.status === "archived") throw notFound("Slack connection not found"); + if (!actor.bypassPermissionCheck && !await accessService(db).hasPermission(row.endpoint.companyId, "user", actor.userId, "tools:manage_connections")) + throw forbidden("Missing permission: tools:manage_connections"); + return row; + } + function assertOrigins(row: Registration, publicId: string) { + if (row.callbackUri !== callbackUri()) throw conflict("The Paperclip URL changed. Restore its configured address or update the Slack app and use manual setup."); + const expected = `${origin(options.webhookOrigin())}/api/chat-webhooks/${publicId}/slack`; + if (object(object(row.manifest.settings).event_subscriptions).request_url !== expected) + throw conflict("The Slack webhook address changed. Restore its configured address or update the Slack app and use manual setup."); + } + async function audit(row: Registration, actor: SlackSetupActor, action: string, code?: string) { + await logActivity(db, { companyId: row.companyId, actorType: "user", actorId: actor.userId, + action: `chat_slack.${action}`, entityType: "chat_endpoint", entityId: row.endpointId, + details: { endpointId: row.endpointId, appId: row.appId, ...(code ? { code } : {}) } }); + } + const api = createSlackSetupClient(fetchImpl); + async function saveSecrets(row: Registration, values: Record, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard, + patch: Partial = {}, account?: SlackAccountState) { + await db.transaction(async tx => { + await lease.assertOwned(tx); + const current = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)).for("update"))[0]; + if (!current || current.requestId !== row.requestId || current.status === "removed") throw conflict("Slack setup changed. Resume the saved connection."); + const ids = { ...current.secretIds }; + for (const [key, value] of Object.entries(values)) { + const result = await writeConnectionCredential(tx, { companyId: row.companyId, connectionName: "Slack app registration", + configPath: `slack_registration.${key}`, label: key, value, actor: { userId: actor.userId }, + ...(ids[key] ? { existingRef: { secretId: ids[key], configPath: `slack_registration.${key}` } } : {}) }); + ids[key] = result.secret.id; + } + await notifyChatVerificationWork(tx); + await tx.update(chatSlackRegistrations).set({ ...patch, secretIds: ids, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, row.endpointId)); + if (account) { + const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; + if (!saved || saved.status === "archived") throw conflict("Slack setup changed"); + // Reauthorization never changes an established personal account binding. + if (saved.setup.slackAccount?.status !== "linked") await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAccount: account }, updatedAt: new Date() }).where(eq(chatEndpoints.id, row.endpointId)); + } + await lease.assertOwned(tx); + }); + } + async function readSecret(row: Registration, key: string) { + if (!row.secretIds[key]) throw conflict("Slack setup credentials are unavailable. Resume the saved connection."); + return vault.resolveSecretValue(row.companyId, row.secretIds[key], "latest", { + accessContext: { consumerType: "system", consumerId: `slack-registration:${row.endpointId}`, configPath: `slack_registration.${key}`, actorType: "system" }, + }); + } + async function setFailure(row: Registration, status: Registration["status"], code: string, lease: CredentialMutationLeaseGuard) { + await lease.assertOwned(); + await db.update(chatSlackRegistrations).set({ status, errorCode: code, updatedAt: new Date() }) + .where(and(eq(chatSlackRegistrations.endpointId, row.endpointId), eq(chatSlackRegistrations.requestId, row.requestId))); + } + async function saveAvatar(row: Registration, avatar: SlackAvatarState, actor: SlackSetupActor, lease: CredentialMutationLeaseGuard) { + await endpoint(row.endpointId, actor); + await db.transaction(async tx => { + await lease.assertOwned(tx); + const current = (await tx.select().from(chatSlackRegistrations).where(eq(chatSlackRegistrations.endpointId, row.endpointId)).for("update"))[0]; + if (!current || current.requestId !== row.requestId || current.status === "removed") throw conflict("Slack setup changed. Resume the saved connection."); + const saved = (await tx.select().from(chatEndpoints).where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))).for("update"))[0]; + if (!saved || saved.status === "archived" || !isProvisionedSlackSetup(saved.setup.slackSetupMethod)) throw conflict("Slack setup changed. Resume the saved connection."); + await tx.update(chatEndpoints).set({ setup: { ...saved.setup, slackAvatar: avatar }, updatedAt: new Date() }) + .where(and(eq(chatEndpoints.id, row.endpointId), eq(chatEndpoints.companyId, row.companyId))); + await lease.assertOwned(tx); + }); + } + const renderAvatar = (request: AgentAvatarRequest) => (options.renderAvatar ?? (avatarPool ??= createAgentAvatarPool()).render)(request); + return { db, options, origin, callbackUri, registration, endpoint, assertOrigins, audit, api, saveSecrets, readSecret, setFailure, saveAvatar, renderAvatar, + close: async () => { await avatarPool?.close(); } }; +} +export type SlackRegistrationStore = ReturnType; diff --git a/server/src/services/connectors/slack/setup/service.ts b/server/src/services/connectors/slack/setup/service.ts new file mode 100644 index 00000000000..f6b61281aea --- /dev/null +++ b/server/src/services/connectors/slack/setup/service.ts @@ -0,0 +1,151 @@ +import { isProvisionedSlackSetup } from "@paperclipai/shared"; +import { createSlackManagerGrants } from "./manager-grants.js"; +import { createManagedSlackSetup } from "./managed.js"; +import { randomBytes } from "node:crypto"; +import { and, eq, gt, like } from "drizzle-orm"; +import { chatEndpoints, chatSlackRegistrations, companies, toolOauthStates, type Db } from "@paperclipai/db"; +import { badRequest, conflict, forbidden, notFound } from "../../../../errors.js"; +import { removeSlackRegistration } from "../../../chat-slack-registration-cleanup.js"; +import type { CredentialMutationLeaseGuard } from "../../../chat-credential-mutation-lease.js"; +import { object, providerFailure, createSlackRegistrationStore, type SlackSetupOptions, type SlackSetupActor } from "./registration.js"; +import { createCustomerOwnedSetup } from "./customer-owned.js"; +import { createSlackCompletion } from "./completion.js"; +const prefix = "slack-install."; +export function slackChatRegistrationService(db: Db, options: SlackSetupOptions) { + const store = createSlackRegistrationStore(db, options); + const { endpoint, registration, assertOrigins, callbackUri, audit, api, readSecret, setFailure, saveSecrets } = store; + const provisioner = createCustomerOwnedSetup(store); + const { create } = provisioner; + const completion = createSlackCompletion(store); + const { resumeLocked, notifyVerified, processPendingVerificationMessages, nextVerificationMessageAt } = completion; + const managerGrants = createSlackManagerGrants(store); + const managed = createManagedSlackSetup(store, managerGrants, provisioner, completion, install); + async function install(endpointId: string, actor: SlackSetupActor) { + return options.withLock(endpointId, async lease => { + const current = await endpoint(endpointId, actor); + const row = await registration(endpointId); + if (!row?.appId || !row.clientId || row.status === "removed") throw conflict("Create this Slack app before installing it"); + if (row.errorCode === "slack_manifest_update_pending") throw conflict("Finish configuring the saved Slack app before installing it"); + if (!isProvisionedSlackSetup(current.endpoint.setup.slackSetupMethod) || current.endpoint.status === "paused") throw conflict("Resume this connection before installing it"); + if (row.managerGrantId) await managerGrants.token(row.managerGrantId, row.companyId, actor); + assertOrigins(row, current.endpoint.publicId); + const state = `${prefix}${randomBytes(32).toString("base64url")}`; + const expiresAt = new Date(Date.now() + 10 * 60_000); + const scopes = object(object(row.manifest.oauth_config).scopes).bot as string[]; + await db.transaction(async tx => { + await lease.assertOwned(tx); + await tx.delete(toolOauthStates).where(and(eq(toolOauthStates.connectionId, current.connection.id), like(toolOauthStates.state, `${prefix}%`))); + await tx.insert(toolOauthStates).values({ state, companyId: row.companyId, connectionId: current.connection.id, + codeVerifier: JSON.stringify({ endpointId, requestId: row.requestId, appId: row.appId, callbackUri: row.callbackUri, + endpointStatus: current.endpoint.status, runtimeGeneration: object(current.endpoint.setup).runtimeGeneration ?? 0 }), + createdByActorType: "user", createdByActorId: actor.userId, createdBySessionId: actor.sessionId, + requestedScopes: scopes, expiresAt }); + await tx.update(chatSlackRegistrations).set({ errorCode: row.managerGrantId ? row.errorCode : null, updatedAt: new Date() }).where(eq(chatSlackRegistrations.endpointId, endpointId)); + }); + const url = new URL("https://slack.com/oauth/v2/authorize"); + url.search = new URLSearchParams({ client_id: row.clientId, scope: scopes.join(","), state, redirect_uri: row.callbackUri, + ...(row.workspaceId ? { team: row.workspaceId } : {}) }).toString(); + await audit(row, actor, "installation_started"); + return { authorizationUrl: url.toString(), expiresAt: expiresAt.toISOString() }; + }); + } + async function expiredReturn(state: string, actor: SlackSetupActor) { + if (!/^slack-install\.[A-Za-z0-9_-]{43}$/.test(state)) return null; + const [attempt] = await db.select().from(toolOauthStates).where(eq(toolOauthStates.state, state)); + if (!attempt || attempt.expiresAt.getTime() > Date.now() || attempt.createdByActorId !== actor.userId || attempt.createdBySessionId !== actor.sessionId) return null; + const binding = object(JSON.parse(attempt.codeVerifier)); + const current = await endpoint(String(binding.endpointId), actor); + return options.withLock(current.endpoint.id, async lease => { + const latest = await endpoint(current.endpoint.id, actor); + const row = await registration(current.endpoint.id); + if (!row || row.status === "removed" || row.requestId !== binding.requestId || row.companyId !== attempt.companyId || latest.connection.id !== attempt.connectionId + || !isProvisionedSlackSetup(latest.endpoint.setup.slackSetupMethod) || row.appId !== binding.appId || row.callbackUri !== binding.callbackUri) return null; + assertOrigins(row, latest.endpoint.publicId); + await lease.assertOwned(); + await db.delete(toolOauthStates).where(eq(toolOauthStates.state, state)); + await setFailure(row, row.status, "slack_install_expired", lease); + await audit(row, actor, "installation_failed", "slack_install_expired"); + return returnPath(row.endpointId); + }); + } + async function pending(state: string, actor: SlackSetupActor) { + if (!/^slack-install\.[A-Za-z0-9_-]{43}$/.test(state)) throw badRequest("Invalid Slack authorization state"); + const [attempt] = await db.select().from(toolOauthStates).where(and(eq(toolOauthStates.state, state), gt(toolOauthStates.expiresAt, new Date()))); + if (!attempt || attempt.createdByActorId !== actor.userId || attempt.createdBySessionId !== actor.sessionId) + throw forbidden("Slack authorization expired or belongs to another session. Resume setup and install again."); + const binding = object(JSON.parse(attempt.codeVerifier)); + const current = await endpoint(String(binding.endpointId), actor); + const row = await registration(current.endpoint.id); + if (!row || row.status === "removed" || !isProvisionedSlackSetup(current.endpoint.setup.slackSetupMethod) + || current.endpoint.status === "paused" + || current.endpoint.status === "revoked" && binding.endpointStatus !== "revoked" + || binding.runtimeGeneration !== (object(current.endpoint.setup).runtimeGeneration ?? 0) + || row.requestId !== binding.requestId || row.appId !== binding.appId || row.callbackUri !== binding.callbackUri + || row.callbackUri !== callbackUri() || attempt.companyId !== row.companyId || attempt.connectionId !== current.connection.id) + throw conflict("Slack setup changed. Resume the saved connection and install again."); + if (row.managerGrantId) await managerGrants.token(row.managerGrantId, row.companyId, actor); + assertOrigins(row, current.endpoint.publicId); + return { attempt, current, row }; + } + async function complete(state: string, code: string | null, error: string | null, actor: SlackSetupActor) { + const before = await pending(state, actor); + return options.withLock(before.row.endpointId, async lease => { + const { row, attempt, current } = await pending(state, actor); + const [claimed] = await db.delete(toolOauthStates).where(and(eq(toolOauthStates.state, state), gt(toolOauthStates.expiresAt, new Date()))).returning(); + if (!claimed) throw conflict("This Slack authorization was already used"); + if (error) { + const failureCode = error === "access_denied" ? "slack_install_declined" : "slack_install_failed"; + await setFailure(row, row.status, failureCode, lease); + await audit(row, actor, "installation_failed", failureCode); + return row.endpointId; + } + try { + if (!code || code.length > 4096) throw providerFailure("slack_install_failed"); + const result = await api("oauth.v2.access", { code, client_id: row.clientId!, client_secret: await readSecret(row, "clientSecret"), redirect_uri: row.callbackUri }); + const teamId = object(result.team).id; + const installerId = object(result.authed_user).id; + if (result.app_id !== row.appId || result.token_type !== "bot" || typeof result.access_token !== "string" || !result.access_token.startsWith("xoxb-") + || typeof teamId !== "string" || !/^T[A-Z0-9]+$/.test(teamId) || typeof result.bot_user_id !== "string" || !/^[UW][A-Z0-9]+$/.test(result.bot_user_id) + || row.workspaceId && row.workspaceId !== teamId || row.botUserId && row.botUserId !== result.bot_user_id) + throw providerFailure("slack_install_identity_mismatch"); + const granted = new Set(String(result.scope ?? "").split(",")); + if ((attempt.requestedScopes ?? []).some(scope => !granted.has(scope))) throw providerFailure("slack_install_scopes_missing"); + if (current.endpoint.providerAccountId && current.endpoint.providerAccountId !== teamId + || current.endpoint.botExternalId && current.endpoint.botExternalId !== result.bot_user_id) + throw providerFailure("slack_install_identity_mismatch"); + if (typeof installerId !== "string" || !/^[UW][A-Z0-9]+$/.test(installerId) || installerId === result.bot_user_id) throw providerFailure("slack_install_account_missing"); + if (row.managerGrantId) { + const grant = await managerGrants.get(row.managerGrantId, row.companyId, actor); + if (grant.status !== "active" || installerId !== grant.slackUserId || teamId !== grant.workspaceId) throw providerFailure("slack_install_identity_mismatch"); + } + await endpoint(row.endpointId, actor); + // Reauthorization reuses the runtime signing secret after staging was cleaned. + await saveSecrets(row, { botToken: result.access_token }, actor, lease, + { status: "credentials_saved", workspaceId: teamId, botUserId: result.bot_user_id, errorCode: null }, + { externalUserId: installerId, paperclipUserId: actor.userId, status: "pending", welcomeStatus: "pending" }); + } catch (failure) { + const code = object(object(failure).details).code; + const safeCode = typeof code === "string" && code.startsWith("slack_install_") ? code : "slack_install_failed"; + await setFailure(row, row.status, safeCode, lease); + await audit(row, actor, "installation_failed", safeCode); + return row.endpointId; + } + await resumeLocked(row.endpointId, actor, lease); + return row.endpointId; + }); + } + const cleanup = (endpointId: string, lease: CredentialMutationLeaseGuard) => removeSlackRegistration(db, endpointId, lease); + async function returnPath(endpointId: string) { + const [row] = await db.select({ prefix: companies.issuePrefix }).from(chatEndpoints) + .innerJoin(companies, eq(companies.id, chatEndpoints.companyId)).where(eq(chatEndpoints.id, endpointId)); + if (!row) throw notFound("Slack connection not found"); + return `/${encodeURIComponent(row.prefix)}/apps/chat/connect?provider=slack&resume=${encodeURIComponent(endpointId)}`; + } + return { create, managerGrants, managed, install, pending, expiredReturn, complete, cleanup, returnPath, registration, notifyVerified, processPendingVerificationMessages, nextVerificationMessageAt, + close: store.close, + resume: (endpointId: string, actor: SlackSetupActor) => options.withLock(endpointId, async lease => { + const row = await registration(endpointId); + if (row?.managerGrantId) await managerGrants.token(row.managerGrantId, row.companyId, actor); + return resumeLocked(endpointId, actor, lease); + }) }; +} diff --git a/server/src/services/connectors/slack/test-message.ts b/server/src/services/connectors/slack/test-message.ts new file mode 100644 index 00000000000..47996f0acf5 --- /dev/null +++ b/server/src/services/connectors/slack/test-message.ts @@ -0,0 +1,134 @@ +import { and, eq, gte, inArray, ne } from "drizzle-orm"; +import { agents, chatActions, chatEndpoints, chatExternalPrincipals, chatIdentityLinks, chatSlackManagerGrants, companyMemberships, toolConnections, type Db } from "@paperclipai/db"; +import { slackTestMessageSchema, type SlackTestMessageInput, type SlackTestMessageResult } from "@paperclipai/shared"; +import { conflict, forbidden, notFound, tooManyRequests, unprocessable } from "../../../errors.js"; +import { logger } from "../../../middleware/logger.js"; +import { logActivity } from "../../activity-log.js"; +import { instanceSettingsService } from "../../instance-settings.js"; +import { createSlackSetupClient, object } from "./setup/provider-client.js"; +import type { SlackSetupActor, SlackSetupOptions } from "./setup/registration.js"; + +type Options = Pick & { + ensureAccount: (endpointId: string, user: Record) => Promise; + createLinkIntent: (endpointId: string, principalId: string, expiresInSeconds: number) => Promise<{ confirmationUrl: string; expiresAt: string }>; +}; +const human = (user: Record, workspaceId: string) => + typeof user.id === "string" && /^[UW][A-Z0-9]+$/.test(user.id) && user.team_id === workspaceId && + user.is_bot === false && user.is_app_user !== true && user.deleted !== true && user.id !== "USLACKBOT"; + +export function slackTestMessageService(db: Db, options: Options) { + const api = createSlackSetupClient(options.fetch); + return async (endpointId: string, actor: SlackSetupActor, rawInput: SlackTestMessageInput): Promise => { + const input = slackTestMessageSchema.parse(rawInput); + if (!(await instanceSettingsService(db).getExperimental()).enableChatConnectors) throw forbidden("Chat connectors are disabled"); + return options.withLock(endpointId, async lease => { + const [record] = await db.select({ endpoint: chatEndpoints, agentName: agents.name }).from(chatEndpoints) + .innerJoin(agents, and(eq(agents.id, chatEndpoints.assignedAgentId), eq(agents.companyId, chatEndpoints.companyId))) + .innerJoin(toolConnections, and(eq(toolConnections.id, chatEndpoints.connectionId), eq(toolConnections.companyId, chatEndpoints.companyId), eq(toolConnections.enabled, true), eq(toolConnections.status, "active"))) + .where(eq(chatEndpoints.id, endpointId)); + if (!record || record.endpoint.provider !== "slack") throw notFound("Active Slack connection not found"); + const endpoint = record.endpoint; + if (!["active", "verifying"].includes(endpoint.status) || !endpoint.providerAccountId || !endpoint.allowDirectMessages) throw conflict("Enable direct messages on an active Slack connection first"); + const [membership] = await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, endpoint.companyId), eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, actor.userId), eq(companyMemberships.status, "active"), ne(companyMemberships.membershipRole, "viewer"), + )); + if (!membership) throw forbidden("An active Paperclip account is required"); + const actionKey = `slack_test_message:${actor.userId}:${input.requestId}`; + const [previous] = await db.select().from(chatActions).where(and(eq(chatActions.endpointId, endpointId), eq(chatActions.companyId, endpoint.companyId), eq(chatActions.providerActionId, actionKey))); + if (previous) { + if (previous.status === "processed" && typeof previous.result?.slackUrl === "string") return { status: "sent", slackUrl: previous.result.slackUrl }; + throw conflict("This message may already have been sent. Check your Slack DMs before sending another."); + } + const recent = await db.select({ id: chatActions.id }).from(chatActions).where(and( + eq(chatActions.endpointId, endpointId), eq(chatActions.companyId, endpoint.companyId), inArray(chatActions.kind, ["slack_test_message", "slack_test_message_invitation"]), gte(chatActions.createdAt, new Date(Date.now() - 60_000)), + )).limit(5); + if (recent.length >= 5) throw tooManyRequests("Wait a minute before sending another test message"); + const links = await db.select({ endpointId: chatIdentityLinks.endpointId, slackUserId: chatExternalPrincipals.externalId }).from(chatIdentityLinks) + .innerJoin(chatExternalPrincipals, and(eq(chatExternalPrincipals.id, chatIdentityLinks.principalId), eq(chatExternalPrincipals.companyId, chatIdentityLinks.companyId))) + .where(and(eq(chatIdentityLinks.companyId, endpoint.companyId), eq(chatIdentityLinks.paperclipUserId, actor.userId), eq(chatIdentityLinks.status, "linked"), + eq(chatExternalPrincipals.provider, "slack"), eq(chatExternalPrincipals.providerAccountId, endpoint.providerAccountId), eq(chatExternalPrincipals.isBot, false))); + const current = links.filter(link => link.endpointId === endpointId); + const grants = await db.select({ slackUserId: chatSlackManagerGrants.slackUserId }).from(chatSlackManagerGrants).where(and( + eq(chatSlackManagerGrants.companyId, endpoint.companyId), eq(chatSlackManagerGrants.userId, actor.userId), eq(chatSlackManagerGrants.workspaceId, endpoint.providerAccountId), eq(chatSlackManagerGrants.status, "active"), + )); + const known = [...new Set((current.length ? current : [...links, ...grants]).map(link => link.slackUserId))]; + const [invitation] = await db.select().from(chatActions).where(and(eq(chatActions.endpointId, endpointId), eq(chatActions.companyId, endpoint.companyId), eq(chatActions.providerActionId, `${actionKey}:invite`))); + const invitedUser = typeof invitation?.payload.slackUserId === "string" ? invitation.payload.slackUserId : undefined; + if (invitedUser && input.slackUserId && input.slackUserId !== invitedUser) throw conflict("This request already invited a different Slack account"); + const userId = input.slackUserId ?? invitedUser ?? (known.length === 1 ? known[0] : undefined); + const token = await options.runtimeBotToken(endpointId); + if (!token) throw conflict("Reconnect this Slack app before sending a message"); + async function call(method: string, fields: Record) { + try { return await api(method, fields, token); } + catch { throw unprocessable("Slack could not complete this request. Check the connection and try again."); } + } + if (!userId) { + const accounts: { id: string; name: string }[] = []; + const result = await call("users.list", { limit: "200", ...(input.accountCursor ? { cursor: input.accountCursor } : {}) }); + for (const value of Array.isArray(result.members) ? result.members : []) { + const user = object(value); + if (human(user, endpoint.providerAccountId)) accounts.push({ id: String(user.id), name: String(user.real_name || user.name || user.id) }); + } + const cursor = object(result.response_metadata).next_cursor; + return { status: "connection_required", accounts, nextCursor: typeof cursor === "string" && cursor ? cursor : null }; + } + const user = object((await call("users.info", { user: userId })).user); + if (user.id !== userId || !human(user, endpoint.providerAccountId)) throw unprocessable("Choose your personal Slack account in this workspace"); + if (!known.includes(userId)) { + const principalId = await options.ensureAccount(endpointId, user); + const principalLinks = await db.select().from(chatIdentityLinks).where(and(eq(chatIdentityLinks.companyId, endpoint.companyId), eq(chatIdentityLinks.principalId, principalId))); + if (principalLinks.some(link => link.status === "linked" && link.paperclipUserId !== actor.userId) + || principalLinks.some(link => link.endpointId === endpointId && link.status === "revoked")) throw conflict("This Slack account has an existing link"); + if (invitation) { + if (invitation.status !== "processed") throw conflict("The invitation may already have been sent. Check Slack before requesting another."); + const expiresAt = String(invitation.result?.expiresAt ?? ""); + if (!expiresAt || new Date(expiresAt).getTime() <= Date.now()) throw conflict("The Slack invitation expired. Request a new test message."); + return { status: "connection_pending", principalId, expiresAt }; + } + // The account holder must follow the private Slack challenge and sign in. + // Selecting a workspace member does not prove identity ownership. + const intent = await options.createLinkIntent(endpointId, principalId, 600); + const [pending] = await db.insert(chatActions).values({ companyId: endpoint.companyId, endpointId, principalId, + kind: "slack_test_message_invitation", providerActionId: `${actionKey}:invite`, status: "processing", payload: { userId: actor.userId, slackUserId: userId } }).returning(); + try { + const response = await call("chat.postMessage", { channel: userId, + text: `Connect your Slack account to ${record.agentName} in Paperclip to receive your test message.`, + blocks: JSON.stringify([{ type: "section", text: { type: "plain_text", text: `Connect your Slack account to ${record.agentName} in Paperclip.` } }, + { type: "section", text: { type: "mrkdwn", text: `<${intent.confirmationUrl}|Connect my account> to receive your test message.` } }]), + unfurl_links: "false", unfurl_media: "false" }); + if (typeof response.channel !== "string" || !/^D[A-Z0-9]+$/.test(response.channel) || typeof response.ts !== "string") throw unprocessable("Slack did not confirm invitation delivery"); + await lease.assertOwned(); + await db.update(chatActions).set({ status: "processed", result: { expiresAt: intent.expiresAt }, updatedAt: new Date() }).where(eq(chatActions.id, pending!.id)); + } catch { + await db.update(chatActions).set({ status: "uncertain", result: { code: "slack_invitation_unconfirmed" }, updatedAt: new Date() }).where(eq(chatActions.id, pending!.id)); + throw unprocessable("Invitation delivery was not confirmed. Check Slack before requesting another."); + } + try { await logActivity(db, { companyId: endpoint.companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.test_message_invited", entityType: "tool_connection", entityId: endpoint.connectionId, details: { endpointId, slackUserId: userId } }); } + catch { logger.warn({ endpointId }, "Slack test invitation activity could not be recorded"); } + return { status: "connection_pending", principalId, expiresAt: intent.expiresAt }; + } + await options.linkInstaller(endpointId, user, actor, lease); + await logActivity(db, { companyId: endpoint.companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.test_message_requested", entityType: "tool_connection", entityId: endpoint.connectionId, details: { endpointId, slackUserId: userId } }); + await lease.assertOwned(); + // Persist before dispatch: a crash or ambiguous response must never replay the DM. + const [action] = await db.insert(chatActions).values({ companyId: endpoint.companyId, endpointId, kind: "slack_test_message", providerActionId: actionKey, + status: "processing", payload: { userId: actor.userId, slackUserId: userId } }).returning(); + let slackUrl: string; + try { + const response = await call("chat.postMessage", { channel: userId, + text: `Hi, I’m ${record.agentName}. This is your test message. Reply here to talk to me.`, unfurl_links: "false", unfurl_media: "false" }); + if (typeof response.channel !== "string" || !/^D[A-Z0-9]+$/.test(response.channel) || typeof response.ts !== "string") throw unprocessable("Slack did not confirm delivery. Check your DMs before trying again."); + slackUrl = `https://app.slack.com/client/${encodeURIComponent(endpoint.providerAccountId)}/${response.channel}`; + await lease.assertOwned(); + await db.update(chatActions).set({ status: "processed", result: { slackUrl }, updatedAt: new Date() }).where(eq(chatActions.id, action!.id)); + } catch { + await db.update(chatActions).set({ status: "uncertain", result: { code: "slack_test_message_unconfirmed" }, updatedAt: new Date() }).where(eq(chatActions.id, action!.id)); + throw unprocessable("Delivery was not confirmed. Check your Slack DMs before sending another test message."); + } + try { await logActivity(db, { companyId: endpoint.companyId, actorType: "user", actorId: actor.userId, action: "chat_slack.test_message_sent", entityType: "tool_connection", entityId: endpoint.connectionId, details: { endpointId, slackUserId: userId } }); } + catch { logger.warn({ endpointId }, "Slack test message activity could not be recorded"); } + return { status: "sent", slackUrl }; + }); + }; +} diff --git a/server/src/services/paperclip-cloud-connector.test.ts b/server/src/services/paperclip-cloud-connector.test.ts index 42e8a3cd4bb..a2a0b37abe0 100644 --- a/server/src/services/paperclip-cloud-connector.test.ts +++ b/server/src/services/paperclip-cloud-connector.test.ts @@ -7,6 +7,7 @@ import { randomBytes, type KeyObject, } from "node:crypto"; +import { SLACK_MANAGER_SCOPES } from "@paperclipai/shared"; import { describe, expect, it, vi } from "vitest"; import { readFileSync } from "node:fs"; @@ -406,6 +407,16 @@ describe("Paperclip Cloud connector", () => { })).rejects.toMatchObject({ code: "CONNECTOR_BAD_RESPONSE" }); }); + it("opens Slack manager handoffs with their exact profile and personal identity", async () => { + const keys = config(); + const credentials = { v: 1, accessToken: "manager-access-canary", refreshToken: "manager-refresh-canary", tokenType: "user", accessTokenExpiresAt: "2030-01-01T00:00:00.000Z", refreshTokenExpiresAt: null, + scopes: [...SLACK_MANAGER_SCOPES], subject, companyId, instanceId, environment: "staging", provider: "slack", profile: "slack.manager", + slackManager: { appId: "AMANAGER", workspaceId: "TWORKSPACE", workspaceName: "Paperclip", userId: "UOWNER" } }; + const sealed = seal(credentials, keys.sealPublicKey, "initial", keys.config, "slack.manager"); + const connector = createPaperclipCloudConnector({ config: keys.config, request: vi.fn(async () => Response.json({ claimId: "claim", scopes: [...SLACK_MANAGER_SCOPES], sealed })) as typeof fetch }); + await expect(connector.claim({ subject, companyId, profile: "slack.manager", claimId: "claim", redemptionId: "manager-state" })).resolves.toEqual(credentials); + await expect(connector.claim({ subject: "another-user", companyId, profile: "slack.manager", claimId: "claim", redemptionId: "manager-state" })).rejects.toThrow(); + }); it("opens an instance-sealed claim and verifies its user, company, and exact scopes", async () => { const keys = config(); const credentials = { @@ -612,10 +623,10 @@ function seal( recipientPublicKey: KeyObject, purpose: "initial" | "access" | "events", configValue: PaperclipCloudConnectorConfig, - profile: keyof typeof GOOGLE_WORKSPACE_CONNECTOR_PROFILES | "github.bot" | "github.code", + profile: keyof typeof GOOGLE_WORKSPACE_CONNECTOR_PROFILES | "slack.manager" | "github.bot" | "github.code", ) { - const provider = profile.startsWith("github.") ? "github" : "google"; - const scopes = provider === "github" ? [] : GOOGLE_WORKSPACE_CONNECTOR_PROFILES[profile as keyof typeof GOOGLE_WORKSPACE_CONNECTOR_PROFILES].scopes; + const provider = profile.startsWith("github.") ? "github" : profile === "slack.manager" ? "slack" : "google"; + const scopes = provider === "github" ? [] : profile === "slack.manager" ? SLACK_MANAGER_SCOPES : GOOGLE_WORKSPACE_CONNECTOR_PROFILES[profile as keyof typeof GOOGLE_WORKSPACE_CONNECTOR_PROFILES].scopes; const ephemeral = generateKeyPairSync("x25519"); const ephemeralJwk = ephemeral.publicKey.export({ format: "jwk" }) as { x: string }; const recipientJwk = recipientPublicKey.export({ format: "jwk" }) as { x: string }; diff --git a/server/src/services/paperclip-cloud-connector.ts b/server/src/services/paperclip-cloud-connector.ts index 7785deeb596..241ff937b57 100644 --- a/server/src/services/paperclip-cloud-connector.ts +++ b/server/src/services/paperclip-cloud-connector.ts @@ -1,4 +1,4 @@ -import { ASANA_CONNECTOR_SCOPES, isAsanaConnectorProfileId, type AsanaConnectorProfileId } from "@paperclipai/shared"; +import { SLACK_MANAGER_SCOPES, ASANA_CONNECTOR_SCOPES, isAsanaConnectorProfileId, type AsanaConnectorProfileId } from "@paperclipai/shared"; import { createDecipheriv, createHash, @@ -34,8 +34,8 @@ export { GOOGLE_WORKSPACE_CONNECTOR_PROFILES }; export type PaperclipCloudConnectorEnvironment = "development" | "staging" | "production"; export type PaperclipCloudConnectorOperation = "status" | "session" | "claim" | "refresh" | "revoke" | "webhook-bind" | "event-lease" | "event-ack" | "github-app"; -export type PaperclipCloudConnectorProfileId = GoogleWorkspaceConnectorProfileId | GitHubConnectorProfileId | AsanaConnectorProfileId | "github.bot"; -export type PaperclipCloudConnectorProvider = "google" | "github" | "asana"; +export type PaperclipCloudConnectorProfileId = GoogleWorkspaceConnectorProfileId | GitHubConnectorProfileId | AsanaConnectorProfileId | "slack.manager" | "github.bot"; +export type PaperclipCloudConnectorProvider = "google" | "github" | "asana" | "slack"; export type PaperclipCloudConnectorConfig = { baseUrl: string; @@ -60,6 +60,7 @@ export type SealedConnectorCredentials = { provider: PaperclipCloudConnectorProvider; profile: string; appSlug?: string; + slackManager?: { appId: string; workspaceId: string; workspaceName: string; userId: string }; }; export type SealedGmailCredentials = SealedConnectorCredentials & { provider: "google" }; @@ -95,6 +96,7 @@ type SealedEnvelope = { }; type ConnectorResponse = { + slackManager?: unknown; confirmationUrl?: unknown; authorizationUrl?: unknown; handoff?: unknown; @@ -413,6 +415,13 @@ export function createPaperclipCloudConnector(input: { typeof value === "string" && isPaperclipCloudConnectorProfileId(value) ? [value] : [] ))]; }, + async getSlackManagerAppId(): Promise { + const response = await call("status", { subject: "instance-capabilities", companyId: "instance-capabilities" }); + const manager = response.slackManager; + if (response.active !== true || response.status !== "active" || !Array.isArray(response.profiles) || !response.profiles.includes("slack.manager") + || !manager || typeof manager !== "object" || !("appId" in manager) || typeof manager.appId !== "string" || !/^A[A-Z0-9]+$/.test(manager.appId)) return null; + return manager.appId; + }, async startAuthorization(values: { subject: string; companyId: string; profile?: PaperclipCloudConnectorProfileId; returnUri: string; returnState: string }) { const profile = values.profile ?? "gmail.draft"; const response = await call("session", { ...values, profile }); @@ -782,6 +791,7 @@ function connectorProfileDefinition(profile: PaperclipCloudConnectorProfileId): provider: PaperclipCloudConnectorProvider; scopes: readonly string[]; } { + if (profile === "slack.manager") return { provider: "slack", scopes: SLACK_MANAGER_SCOPES }; if (profile === "github.bot") return { provider: "github", scopes: [] }; if (isAsanaConnectorProfileId(profile)) { return { provider: "asana", scopes: ASANA_CONNECTOR_SCOPES }; @@ -796,6 +806,7 @@ function isExpectedProviderAuthorizationUrl( profile: PaperclipCloudConnectorProfileId, url: URL, ): boolean { + if (profile === "slack.manager") return url.origin === "https://slack.com" && url.pathname === "/oauth/v2/authorize"; if (isAsanaConnectorProfileId(profile)) { return url.origin === "https://app.asana.com" && url.pathname === "/-/oauth_authorize"; } @@ -806,7 +817,7 @@ function isExpectedProviderAuthorizationUrl( } function isPaperclipCloudConnectorProfileId(value: string): value is PaperclipCloudConnectorProfileId { - return isGoogleWorkspaceConnectorProfileId(value) || isGitHubConnectorProfileId(value) || isAsanaConnectorProfileId(value); + return value === "slack.manager" || isGoogleWorkspaceConnectorProfileId(value) || isGitHubConnectorProfileId(value) || isAsanaConnectorProfileId(value); } async function sha256Base64Url(value: string): Promise { diff --git a/tests/e2e/chat-adapters-ui-providers.spec.ts b/tests/e2e/chat-adapters-ui-providers.spec.ts index c64cb950ca8..a7795120a94 100644 --- a/tests/e2e/chat-adapters-ui-providers.spec.ts +++ b/tests/e2e/chat-adapters-ui-providers.spec.ts @@ -156,6 +156,114 @@ test.describe.serial("native chat adapter UI", () => { seed = await seedCompanyAndAgent(request); }); + test("Slack: managed workspace setup has two stops, resumes and completes without pasting credentials", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true }); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat`); + await selectMaya(page); + await expect(page.getByRole("navigation", { name: "Connection setup progress" }).getByRole("button")).toHaveCount(2); + await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible(); + await expect(page.getByLabel("App configuration access token", { exact: true })).toHaveCount(0); + await expect(page.getByLabel("Signing Secret", { exact: true })).toHaveCount(0); + await page.reload(); + expect(mock.slackCreations).toBe(1); expect(mock.slackInstallations).toBe(1); + mock.setWebhookVerified(); + await expect(page.getByRole("heading", { name: "Success", exact: true })).toBeVisible(); + await expect(page.getByRole("navigation", { name: "Connection setup progress" }).getByRole("button")).toHaveCount(2); + await page.getByRole("button", { name: "Done", exact: true }).click(); + await expect(page).toHaveURL(/\/apps\/chat\/endpoint-slack\/settings$/); + }); + test("Slack: a failed manager refresh reloads workspace authorization before retry", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true }); + let refreshFailed = false; + let authorizationRequests = 0; + await page.route("**/chat-slack/setup-options", async route => { + if (!refreshFailed) return route.fallback(); + await route.fulfill({ json: { managedAvailable: true, defaultMethod: "managed", workspaces: [] } }); + }); + await page.route("**/slack/managed/provision", async route => { + refreshFailed = true; + await route.fulfill({ status: 422, json: { error: "Reconnect your Slack workspace.", details: { code: "slack_manager_reauthorize" } } }); + }); + await page.route("**/slack/managed/authorize", async route => { + authorizationRequests++; + await route.fulfill({ json: { authorizationUrl: "https://slack.test/manager-consent", expiresAt: new Date(Date.now() + 600_000).toISOString() } }); + }); + await page.route("https://slack.test/manager-consent", route => route.fulfill({ contentType: "text/html", body: "Slack authorization fixture" })); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`); + await expect(page.getByRole("alert")).toBeVisible(); + await page.getByRole("button", { name: "Continue", exact: true }).click(); + await expect.poll(() => authorizationRequests).toBe(1); + await expect(page).toHaveURL("https://slack.test/manager-consent"); + }); + test("Slack: every uncertain creation retry needs a fresh confirmation", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true }); + mock.setSlackUncertain(); + const attempts: { requestId: string; confirmedNoAppCreated?: boolean }[] = []; + await page.route("**/slack/managed/provision", async route => { + attempts.push(route.request().postDataJSON()); + await route.fulfill({ json: {} }); + }); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`); + const confirm = page.getByRole("checkbox", { name: "I checked Slack and no app was created. Create a new app." }); + const create = page.getByRole("button", { name: "Continue", exact: true }); + await expect(create).toBeDisabled(); + await confirm.check(); await create.click(); + await expect(confirm).not.toBeChecked(); await expect(create).toBeDisabled(); + expect(attempts).toHaveLength(1); expect(attempts[0]!.confirmedNoAppCreated).toBe(true); + await confirm.check(); await create.click(); + await expect(confirm).not.toBeChecked(); await expect(create).toBeDisabled(); + expect(attempts).toHaveLength(2); expect(attempts[1]!.requestId).not.toBe(attempts[0]!.requestId); + }); + test("Slack: own-app creation consumes confirmation even when the response is lost", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, automaticSlack: true }); + mock.setSlackUncertain(); + let attempts = 0; + await page.route("**/slack/registration", async route => { + attempts++; expect(route.request().postDataJSON().confirmedNoAppCreated).toBe(true); + await route.fulfill({ status: 502, json: { error: "Creation response unavailable" } }); + }); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`); + const confirm = page.getByRole("checkbox"); + await confirm.check(); + await page.getByLabel("App configuration access token", { exact: true }).fill("fixture-config-token"); + await page.getByRole("button", { name: "Create Slack app", exact: true }).click(); + await expect(page.getByRole("alert")).toBeVisible(); + await expect(confirm).not.toBeChecked(); + await expect(page.getByRole("button", { name: "Create Slack app", exact: true })).toBeDisabled(); + expect(attempts).toBe(1); + }); + test("Slack: temporarily unavailable managed setup can retry in place", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true }); + let available = false; + await page.route("**/chat-slack/setup-options", async route => { + if (available) return route.fallback(); + await route.fulfill({ json: { managedAvailable: false, defaultMethod: "automatic", workspaces: [] } }); + }); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat&resume=endpoint-slack`); + await expect(page.getByText("Managed Slack setup is unavailable. Try again later.")).toBeVisible(); + available = true; await page.getByRole("button", { name: "Retry", exact: true }).click(); + await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible(); + }); + test("Slack: own-app selection persists before provisioning", async ({ page }) => { + const slack = PROVIDERS.find(provider => provider.provider === "slack")!; + await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, managedSlack: true }); + await page.goto(`/${seed.prefix}/apps/chat/connect?provider=slack&purpose=chat`); + await page.getByRole("button", { name: "Choose an active agent" }).click(); + await page.getByRole("button", { name: "Select Maya" }).click(); + await page.getByText("Advanced", { exact: true }).click(); + await page.getByRole("button", { name: "Use your own app" }).click(); + await expect(page.getByRole("heading", { name: "App configuration access token", exact: true })).toBeVisible(); + await page.reload(); + await expect(page.getByRole("heading", { name: "App configuration access token", exact: true })).toBeVisible(); + await page.getByText("Advanced", { exact: true }).click(); + await page.getByRole("button", { name: "Use managed setup" }).click(); + await expect(page.getByRole("heading", { name: "Send a message to your agent", exact: true })).toBeVisible(); + }); test("Slack: automatic creation survives refresh, consent, and connection evidence without copying durable secrets", async ({ page }) => { const slack = PROVIDERS.find(provider => provider.provider === "slack")!; const mock = await installChatControlPlaneMock(page, slack, seed, { enableChatConnectors: true, automaticSlack: true }); @@ -440,6 +548,10 @@ test.describe.serial("native chat adapter UI", () => { ).toBeVisible(); await expectSetupRail(page, true); await selectMaya(page); + if (provider.provider === "slack") { + await page.getByText("Advanced", { exact: true }).click(); + await page.getByRole("button", { name: "Create manually", exact: true }).click(); + } await expect.poll(() => mock.createdWithAgentId).toBe(seed.agentId); expect(mock.createdWithAgentId).not.toBe(seed.otherAgentId); await expect( @@ -573,18 +685,12 @@ test.describe.serial("native chat adapter UI", () => { page.getByRole("heading", { name: provider.provider === "slack" ? "Allowed Channels" : "Destinations" }), ).toBeVisible(); if (provider.provider === "slack") { - await page.locator("summary").filter({ hasText: "Message in a channel" }).click(); - await expect(page.getByText("@maya-paperclip you there?", { exact: true })).toBeVisible(); - await expect(page.getByRole("button", { name: "Copy message" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Allowed Channels" })).toBeVisible(); - await page.locator("summary").filter({ hasText: /^Agent avatar$/ }).click(); - const avatarSection = page.getByRole("region", { name: "Slack avatar" }); - await expect(avatarSection.getByRole("link", { name: "Download avatar" })).toBeVisible(); - await avatarSection.getByText("How to upload in Slack", { exact: true }).click(); - await expect(avatarSection.getByRole("link", { name: "Open Slack app Settings" })).toBeVisible(); - const settingsDownloadEvent = page.waitForEvent("download"); - await avatarSection.getByRole("link", { name: "Download avatar" }).click(); - expect((await settingsDownloadEvent).suggestedFilename()).toBe("maya-paperclip-avatar.png"); + await expect(page.getByRole("button", { name: "Send me a test message" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Open Slack configuration" })).toBeVisible(); + await expect(page.getByText("Message in a channel", { exact: true })).toHaveCount(0); + await expect(page.getByText("Agent avatar", { exact: true })).toHaveCount(0); + await expect(page.getByRole("link", { name: "View message" })).toHaveCount(0); } await expect( diff --git a/tests/e2e/chat-adapters-ui.shared.ts b/tests/e2e/chat-adapters-ui.shared.ts index e8904fc98ed..afa8a9686b9 100644 --- a/tests/e2e/chat-adapters-ui.shared.ts +++ b/tests/e2e/chat-adapters-ui.shared.ts @@ -281,7 +281,7 @@ export function endpointFixture(provider: ProviderCase, seed: Seed) { proactiveDirectMessages: false, }, setup: { - slackSetupMethod: undefined as "automatic" | "manual" | "existing" | undefined, + slackSetupMethod: undefined as "managed" | "automatic" | "manual" | "existing" | undefined, slackRegistration: undefined as SlackRegistrationState | undefined, slackApp: undefined as SlackAppConfiguration | undefined, slackOAuthCallbackUri: "https://paperclip.example.test/api/chat-slack/oauth/callback", @@ -353,10 +353,11 @@ export async function installChatControlPlaneMock( resourceCount = 2, photonShared = false, automaticSlack = false, - }: { enableChatConnectors: boolean; enableGitHubReviewBots?: boolean; resourceCount?: number; photonShared?: boolean; automaticSlack?: boolean }, + managedSlack = false, + }: { enableChatConnectors: boolean; enableGitHubReviewBots?: boolean; resourceCount?: number; photonShared?: boolean; automaticSlack?: boolean; managedSlack?: boolean }, ): Promise { const endpoint = endpointFixture(provider, seed); - if (automaticSlack) endpoint.setup.slackSetupMethod = "automatic"; + if (automaticSlack || managedSlack) endpoint.setup.slackSetupMethod = managedSlack ? "managed" : "automatic"; let slackIdentityLinked = false; let githubAppConnected = false; let githubConfiguration = { revision: 0, configuration: { @@ -510,6 +511,7 @@ export async function installChatControlPlaneMock( assignedAgentId: seed.agentId, }); state.createdWithAgentId = String(body.assignedAgentId); + if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "managed" | "automatic" | "manual" | "existing"; if (provider.provider === "slack" && body.slackApp) { endpoint.setup.slackApp = body.slackApp as SlackAppConfiguration; endpoint.setup.command = endpoint.setup.slackApp.command; @@ -536,7 +538,7 @@ export async function installChatControlPlaneMock( if (method === "PATCH") { const body = bodyOf(route); if (body.slackApp) endpoint.setup.slackApp = body.slackApp as SlackAppConfiguration; - if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "automatic" | "manual" | "existing"; + if (body.slackSetupMethod) endpoint.setup.slackSetupMethod = body.slackSetupMethod as "managed" | "automatic" | "manual" | "existing"; if (typeof body.allowDirectMessages === "boolean") state.allowDirectMessages = body.allowDirectMessages; if (typeof body.allowGroupChats === "boolean") @@ -563,6 +565,16 @@ export async function installChatControlPlaneMock( return; } + if (pathname === `/api/companies/${seed.companyId}/chat-slack/setup-options`) { + await fulfill(route, { managedAvailable: managedSlack, defaultMethod: managedSlack ? "managed" : "automatic", workspaces: managedSlack ? [{ grantId: "11111111-1111-4111-8111-111111111111", workspaceId: "TE2E", workspaceName: "Paperclip", userId: "UINSTALLER" }] : [] }); return; + } + if (pathname === `/api/chat-endpoints/${endpoint.id}/slack/managed/provision`) { + const body = bodyOf(route); + expect(body.grantId).toBe("11111111-1111-4111-8111-111111111111"); + expect(body).not.toHaveProperty("credentials"); + state.slackCreations++; state.slackInstallations++; + state.setSlackInstalled(); await fulfill(route, {}); return; + } if (pathname === `/api/chat-endpoints/${endpoint.id}/slack/registration`) { const body = bodyOf(route); expect(body.credentials).toEqual({ configurationToken: "fixture-config-token" }); diff --git a/ui/src/api/chatEndpoints.ts b/ui/src/api/chatEndpoints.ts index a640a8e3f98..73aa8080ef4 100644 --- a/ui/src/api/chatEndpoints.ts +++ b/ui/src/api/chatEndpoints.ts @@ -1,3 +1,4 @@ +import type { SlackSetupOptions, SlackManagedProvisionInput, SlackManagedResult } from "@paperclipai/shared"; import { api } from "./client"; import type { SlackAppConfiguration, @@ -140,7 +141,8 @@ export interface ChatEndpoint { messagingEndpoint?: string | null; command?: string | null; slackApp?: SlackAppConfiguration; - slackSetupMethod?: "automatic" | "manual" | "existing"; + slackSetupMethod?: "managed" | "automatic" | "manual" | "existing"; + slackManagerError?: string; slackRegistration?: SlackRegistrationState; slackAvatar?: SlackAvatarState; slackAccount?: SlackAccountState; @@ -202,7 +204,7 @@ export const chatEndpointsApi = { api.get(`/chat-endpoints/${endpointId}`), create: ( companyId: string, - input: { provider: ChatProvider; assignedAgentId: string; slackApp?: SlackAppConfiguration }, + input: { provider: ChatProvider; assignedAgentId: string; slackApp?: SlackAppConfiguration; slackSetupMethod?: "managed" | "automatic" | "manual" | "existing" }, ) => api.post(`/companies/${companyId}/chat-endpoints`, input), update: ( endpointId: string, @@ -216,6 +218,11 @@ export const chatEndpointsApi = { photon?: PhotonChannelConfiguration; }, ) => api.post(`/chat-endpoints/${endpointId}/setup`, input), + sendSlackTestMessage: (endpointId: string, input: import("@paperclipai/shared").SlackTestMessageInput) => + api.post(`/chat-endpoints/${endpointId}/slack/test-message`, input), + slackSetupOptions: (companyId: string) => api.get(`/companies/${companyId}/chat-slack/setup-options`), + authorizeSlackManager: (endpointId: string) => api.post(`/chat-endpoints/${endpointId}/slack/managed/authorize`, {}), + provisionManagedSlack: (endpointId: string, input: SlackManagedProvisionInput) => api.post(`/chat-endpoints/${endpointId}/slack/managed/provision`, input), createSlackApp: (endpointId: string, input: SlackRegistrationInput) => api.post(`/chat-endpoints/${endpointId}/slack/registration`, input), installSlackApp: (endpointId: string) => diff --git a/ui/src/pages/apps/chat/ChatEndpoint.clipboard.test.tsx b/ui/src/pages/apps/chat/ChatEndpoint.clipboard.test.tsx index dedf6127417..adf87af5089 100644 --- a/ui/src/pages/apps/chat/ChatEndpoint.clipboard.test.tsx +++ b/ui/src/pages/apps/chat/ChatEndpoint.clipboard.test.tsx @@ -503,23 +503,15 @@ describe("chat setup and identity-link clipboard actions", () => { expect(container.querySelector('aside button[aria-current="step"]')?.textContent).toBe("4Send a message"); }); - it("keeps a terminated agent’s saved avatar available in settings", async () => { - mocks.tab = "settings"; - mocks.listAgents.mockResolvedValue([]); - mocks.getAgent.mockResolvedValue({ id: "agent-a", name: "Maya", status: "terminated", appearance: { schemaVersion: 1, characterVersion: "cap-v1", paletteId: "orchid-peach" } }); - await render("slack", true); - expect(container.querySelector('a[download]')?.getAttribute("href")).toBe("/api/agent-avatars/cap-v1/orchid-peach/rest.png?size=512&scale=1&background=paperclip-dark"); - expect(mocks.getAgent).toHaveBeenCalledWith("agent-a", "company-a"); - }); - - it("keeps avatar download available in connector settings", async () => { + it("keeps settings focused on channel access and the test-message action", async () => { mocks.tab = "settings"; await render("slack", true); - const section = container.querySelector('section[aria-label="Slack avatar"]')!; - expect(section.textContent).toContain("Download avatar"); - expect(section.querySelector('a[download]')?.getAttribute("href")).toContain("/cherry-pop/rest.png?size=512&scale=1"); - expect(section.querySelector("details")?.open).toBe(false); - expect(mocks.getAgent).toHaveBeenCalledWith("agent-a", "company-a"); + expect(container.textContent).toContain("Send me a test message"); + expect(container.textContent).toContain("Open Slack configuration"); + expect(container.textContent).not.toContain("Message in a channel"); + expect(container.textContent).not.toContain("Agent avatar"); + expect(container.textContent).not.toContain("Manage action permissions"); + expect(container.querySelector('a[download]')).toBeNull(); }); it("discovers a newly invited, enabled Slack channel while settings stays open", async () => { diff --git a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx index d26e6b75416..dd075cf795e 100644 --- a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx +++ b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx @@ -1,14 +1,13 @@ +import { agentAvatarUrl } from "@/lib/agent-avatar-url"; +import { resolveAgentAppearance } from "@paperclipai/shared"; +import { SlackTestMessageButton } from "./SlackTestMessageButton"; +import { AgentAvatar } from "@/components/AgentAvatar"; import { Identity } from "@/components/Identity"; import { SlackSetupAdvanced } from "./SlackAppDetails"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { SlackToolsSettings, SlackSearchAccess } from "./SlackToolSettings"; -import { defaultSlackAppName } from "./slack-app-name"; import { ChatCommunicationInstructions } from "./ChatCommunicationInstructions"; -import { SlackAvatarSettings } from "./SlackAvatarStep"; import { agentsApi } from "@/api/agents"; -import { agentAvatarUrl } from "@/lib/agent-avatar-url"; -import { resolveAgentAppearance } from "@paperclipai/shared"; -import { AgentAvatar } from "@/components/AgentAvatar"; import { ChatConversationList } from "./ChatConversationList"; import { GitHubBotManagement, GitHubReviews } from "./GitHubBotManagement"; import { GitHubBotMention, GitHubAppIdentityActions } from "./GitHubAppIdentity"; @@ -315,8 +314,9 @@ export function ChatEndpointDetail() { endpoint.setup?.step !== "complete" && !(endpoint.provider === "slack" && endpoint.setup?.step === "test") && ["draft", "verifying", "attention", "revoked"].includes(endpoint.status); - const slackUrl = endpoint.provider === "slack" && endpoint.providerAccountId && endpoint.botExternalId - ? `https://app.slack.com/client/${encodeURIComponent(endpoint.providerAccountId)}/user/${encodeURIComponent(endpoint.botExternalId)}` + const slackAppId = endpoint.setup?.slackRegistration?.appId; + const slackUrl = endpoint.provider === "slack" + ? slackAppId ? `https://api.slack.com/apps/${encodeURIComponent(slackAppId)}/oauth` : "https://api.slack.com/apps" : null; return ( @@ -347,8 +347,8 @@ export function ChatEndpointDetail() { )} -
- {slackUrl && } +
+ {slackUrl && } {setupIncomplete ? (
+ {endpoint.provider === "slack" && } {endpoint.provider === "github" && } @@ -435,13 +436,6 @@ function Settings({ }) { const queryClient = useQueryClient(); const { pushToast } = useToast(); - const [messageCopied, setMessageCopied] = useState(false); - const avatarAgent = useQuery({ - queryKey: queryKeys.agents.detail(endpoint.assignedAgentId), - queryFn: () => agentsApi.get(endpoint.assignedAgentId, endpoint.companyId), - enabled: endpoint.provider === "slack", - }); - const mentionMessage = `@${(endpoint.botUsername ?? endpoint.botLabel ?? endpoint.assignedAgentName).replace(/^@/, "")} you there?`; const resourcesQuery = useQuery({ queryKey: queryKeys.chatEndpoints.resources(endpointId), queryFn: () => chatEndpointsApi.listResources(endpointId), @@ -512,7 +506,7 @@ function Settings({ No destinations yet.

) : ( -
+
{destinationResources.map((resource) => (
@@ -595,27 +589,7 @@ function Settings({ queryClient.setQueryData(queryKeys.chatEndpoints.detail(endpointId), next); }} />} - {endpoint.provider === "slack" && ( -
-

Invite the bot to a channel, then mention it to start a conversation.

-
- {mentionMessage} - -
-
- )} - {endpoint.provider === "slack" && - {avatarAgent.isPending ?

Loading agent avatar…

- : avatarAgent.isError ?

Couldn’t load the agent’s avatar.

- : } -
} - {endpoint.provider === "slack" && } + {endpoint.provider === "slack" && } ); } @@ -634,7 +608,7 @@ function SettingToggle({ onChange: (value: boolean) => void; }) { return ( -
+

{label}

{detail &&

{detail}

} diff --git a/ui/src/pages/apps/chat/ChatEndpointSetup.tsx b/ui/src/pages/apps/chat/ChatEndpointSetup.tsx index fae0c99bb7f..a8d45b008ac 100644 --- a/ui/src/pages/apps/chat/ChatEndpointSetup.tsx +++ b/ui/src/pages/apps/chat/ChatEndpointSetup.tsx @@ -1,6 +1,7 @@ import { buildSlackAppManifest, defaultSlackAppConfiguration } from "@paperclipai/shared"; import { SlackAppDetails, SlackSetupAdvanced } from "./SlackAppDetails"; -import { SlackAutomaticSetup } from "./SlackAutomaticSetup"; +import { SlackSetup } from "./slack/SlackSetup"; +import { isProvisionedSlackSetup, slackSetupState } from "./slack/setup-state"; import { defaultSlackAppName } from "./slack-app-name"; import { GitHubChatSetup } from "./GitHubChatSetup"; import { GitHubAgentTrustWarning } from "@/components/GitHubAgentTrustWarning"; @@ -105,10 +106,11 @@ export function isChatEndpointRepairing( ); } -export function ChatConnectionPurpose({ provider, onChat, onTools }: { +export function ChatConnectionPurpose({ provider, onChat, onTools, slackLabels }: { provider: ChatProvider; onChat: () => void; onTools: () => void; + slackLabels?: string[]; }) { const { setBreadcrumbs } = useBreadcrumbs(); useEffect(() => { @@ -117,7 +119,7 @@ export function ChatConnectionPurpose({ provider, onChat, onTools }: { }, [setBreadcrumbs]); return (
- +

Choose how to connect

@@ -192,6 +194,7 @@ function ChatSdkEndpointSetup() { const [viewedStep, setViewedStep] = useState(null); const [slackIdentityReady, setSlackIdentityReady] = useState(false); const [automaticBusy, setAutomaticBusy] = useState(false); + const [managedAutoStart, setManagedAutoStart] = useState(Boolean(resumeEndpointId)); const [slackDraft, setSlackDraft] = useState<{ agentId: string; app: SlackAppConfiguration } | null>(null); const [endpoint, setEndpoint] = useState(null); const [credentials, setCredentials] = useState>({}); @@ -220,7 +223,7 @@ function ChatSdkEndpointSetup() { useEffect(() => { if (!resumeQuery.data) return; setEndpoint(current => { - if (current?.setup?.slackSetupMethod === "automatic" && !current.providerAccountId && resumeQuery.data.providerAccountId) setViewedStep(null); + if (isProvisionedSlackSetup(current?.setup?.slackSetupMethod) && !current?.providerAccountId && resumeQuery.data.providerAccountId) setViewedStep(null); return resumeQuery.data; }); setAgentId(resumeQuery.data.assignedAgentId); @@ -273,7 +276,8 @@ function ChatSdkEndpointSetup() { next: ChatEndpoint, onlyIfStillVisible = false, ) => { - if (next.setup?.slackSetupMethod === "automatic" && next.setup.slackRegistration?.status === "configured" + if (next.setup?.slackSetupMethod === "managed" && endpoint && endpoint.setup?.slackSetupMethod !== "managed") setManagedAutoStart(true); + if (isProvisionedSlackSetup(next.setup?.slackSetupMethod) && next.setup?.slackRegistration?.status === "configured" && endpoint?.setup?.slackRegistration?.status !== "configured") setViewedStep(null); setEndpoint((visible) => onlyIfStillVisible && visible?.id !== next.id ? visible : next, @@ -287,21 +291,31 @@ function ChatSdkEndpointSetup() { ); } }; + const slackChoices = useQuery({ + queryKey: ["slack-setup-options", selectedCompanyId], + queryFn: () => chatEndpointsApi.slackSetupOptions(selectedCompanyId!), + enabled: provider === "slack" && Boolean(selectedCompanyId), + retry: false, + }); const createEndpoint = useMutation({ - mutationFn: () => - chatEndpointsApi.create(selectedCompanyId!, { + mutationFn: ({ ownApp = false }: { ownApp?: boolean; exit?: boolean }) => { + if (provider === "slack" && !slackChoices.data) throw new Error("Slack setup is still loading. Try again."); + return chatEndpointsApi.create(selectedCompanyId!, { provider: provider!, assignedAgentId: agentId, - ...(provider === "slack" ? { slackApp: slackDetailsValidation.success ? slackDetailsValidation.data : slackDetails } : {}), - }), - onSuccess: (next) => { - setViewedStep(null); + ...(provider === "slack" ? { slackSetupMethod: ownApp ? "automatic" : slackChoices.data!.defaultMethod, slackApp: slackDetailsValidation.success ? slackDetailsValidation.data : slackDetails } : {}), + }); + }, + onSuccess: (next, { exit }) => { + setManagedAutoStart(!exit); + setViewedStep(exit ? 0 : null); syncEndpointSnapshot(next); if (next.provider === "imessage-photon" || next.provider === "slack") { const resumed = new URLSearchParams(params); resumed.set("resume", next.id); setParams(resumed, { replace: true }); } + if (exit) navigate("/apps"); }, onError: (error) => pushToast({ @@ -324,13 +338,22 @@ function ChatSdkEndpointSetup() { if (exit && provider !== "slack") { navigate("/apps"); return; } if (!endpoint) { if (!agentId) { if (exit) navigate("/apps"); return; } - createEndpoint.mutate(undefined, { onSuccess: () => { if (exit) navigate("/apps"); } }); + createEndpoint.mutate({ exit }); return; } try { if (provider === "slack") await persistSlackDetails(); } catch { return; } - if (exit) navigate("/apps"); else setViewedStep(1); + if (exit) navigate("/apps"); else { setManagedAutoStart(true); setViewedStep(null); } }; + const useOwnSlackApp = useMutation({ + mutationFn: async () => { + if (!endpoint) return createEndpoint.mutateAsync({ ownApp: true }); + await persistSlackDetails(); + return chatEndpointsApi.update(endpoint.id, { slackSetupMethod: "automatic" }); + }, + onSuccess: next => { setManagedAutoStart(false); syncEndpointSnapshot(next); setViewedStep(1); }, + onError: () => pushToast({ title: "Couldn't switch Slack setup", body: "Try again.", tone: "error" }), + }); const setupAction = useMutation({ mutationFn: async ({ action, @@ -454,21 +477,14 @@ function ChatSdkEndpointSetup() { reconnectRequested, ); const isSlack = provider === "slack"; - const automaticSlack = isSlack && (!endpoint || endpoint.setup?.slackSetupMethod === "automatic"); - const automaticSlackComplete = automaticSlack && (endpoint?.setup?.step === "test" || endpoint?.setup?.step === "complete"); - const tryStep = isSlack ? automaticSlack ? 3 : 5 : 2; - const availableStep = endpoint - ? automaticSlack && endpoint.setup?.slackRegistration?.status === "configured" && endpoint.setup.slackAccount?.status !== "linked" && endpoint.setup.step !== "complete" ? 2 - : !repairing && - (endpoint.setup?.step === "test" || endpoint.setup?.step === "complete") - ? isSlack && endpoint.setup?.step !== "complete" - ? !automaticSlack && !slackIdentityReady ? 4 : tryStep - : tryStep - : automaticSlack && endpoint.setup?.slackRegistration?.status === "credentials_saved" ? 2 - : isSlack && endpoint.providerAccountId && !repairing ? 3 - : isSlack && (slackCredentialsReady || repairing || endpoint.setup?.slackSetupMethod === "existing" || automaticSlack && endpoint.setup?.slackRegistration?.appId) ? 2 : 1 - : 0; + const slackNavigation = slackSetupState({ endpoint, repairing, credentialsReady: slackCredentialsReady, identityReady: slackIdentityReady, defaultMethod: slackChoices.data?.defaultMethod }); + const automaticSlack = isSlack && slackNavigation.provisioned; + const automaticSlackComplete = automaticSlack && slackNavigation.complete; + const tryStep = isSlack ? slackNavigation.finalStage : 2; + const availableStep = isSlack ? slackNavigation.availableStage : !endpoint ? 0 + : !repairing && (endpoint.setup?.step === "test" || endpoint.setup?.step === "complete") ? tryStep : 1; const step = Math.min(viewedStep ?? availableStep, availableStep); + const agentStepBusy = automaticBusy || createEndpoint.isPending || saveSlackDetails.isPending || useOwnSlackApp.isPending; const slackVerificationQuery = useQuery({ queryKey: ["chat-endpoint-slack-webhook-verification", endpoint?.id], queryFn: () => chatEndpointsApi.get(endpoint!.id), @@ -507,17 +523,17 @@ function ChatSdkEndpointSetup() { ); if (purpose === "choice") { - return setPurpose("chat")} onTools={() => navigate(toolHref)} />; + return setPurpose("chat")} onTools={() => navigate(toolHref)} />; } return (

setViewedStep(isSlack ? slackNavigation.toStage(value) : value)} />
{step === 0 ? ( @@ -541,15 +557,23 @@ function ChatSdkEndpointSetup() { emptyMessage="No active agents are available." />} {isSlack && (selectedAgent || endpoint) && - setSlackDraft({ agentId, app })} onBlur={() => void persistSlackDetails().catch(() => {})} /> {saveSlackDetails.isError &&

Couldn't save the Slack app details. Try again before continuing.

} + {slackChoices.data?.defaultMethod === "managed" && (!endpoint || endpoint.setup?.slackSetupMethod === "managed") && !endpoint?.setup?.slackRegistration && }
} + {isSlack && !endpoint && !slackChoices.data && (slackChoices.isError + ?
Couldn't load Slack setup.
+ :

Loading Slack…

)} {provider === "github" && } - void continueFromAgent(true)}> + void continueFromAgent(true)}>
)}
-

{slackStage === "app" ? automaticSlack ? "App configuration access token" : "Create a Slack app" : automaticSlack ? "Install Slack app" : "Add Slack credentials"}

+

{endpoint.setup?.slackSetupMethod === "managed" ? "Connect to Slack" : slackStage === "app" ? automaticSlack ? "App configuration access token" : "Create a Slack app" : automaticSlack ? "Install Slack app" : "Add Slack credentials"}

{repairing && !automaticSlack && (

Reconnect verifies or replaces credentials for this same Slack app. It does not reinstall the app or change its workspace or channel membership. Leave credentials blank to reuse the saved values. @@ -1628,8 +1655,10 @@ function ProviderConnectStep({

}
- {automaticSlack && {}} onBusy={onAutomaticBusy} onSaved={onEndpointSaved} onContinue={onSlackAppCreated} diff --git a/ui/src/pages/apps/chat/SlackAutomaticSetup.tsx b/ui/src/pages/apps/chat/SlackAutomaticSetup.tsx index 3fe861aaedb..ff342c014a4 100644 --- a/ui/src/pages/apps/chat/SlackAutomaticSetup.tsx +++ b/ui/src/pages/apps/chat/SlackAutomaticSetup.tsx @@ -1,183 +1 @@ -import { useEffect, useRef, useState } from "react"; -import { ExternalLink, Loader2 } from "lucide-react"; -import { slackRegistrationErrorMessage } from "@paperclipai/shared"; -import { chatEndpointsApi, type ChatEndpoint } from "@/api/chatEndpoints"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Checkbox } from "@/components/ui/checkbox"; -import { SetupWizardFooter } from "@/components/SetupWizard"; -import { sanitizedSetupErrorMessage } from "./chat-setup-error"; -import { SlackSetupAdvanced } from "./SlackAppDetails"; - -/** Uses ordinary request-local state: configuration tokens never enter a query/mutation cache. */ -export function SlackAutomaticSetup({ endpoint, stage, disabled, saveDetails, onSaved, onBusy, - onManual, onContinue, onSaveExit }: { - endpoint: ChatEndpoint; - stage: "app" | "credentials"; - disabled: boolean; - saveDetails: () => Promise; - onSaved: (endpoint: ChatEndpoint) => void; - onBusy: (busy: boolean) => void; - onManual: (existing: boolean) => Promise; - onContinue: () => void; - onSaveExit: () => void; -}) { - const [configurationToken, setConfigurationToken] = useState(""); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - const [checkedNoApp, setCheckedNoApp] = useState(false); - const requestId = useRef(crypto.randomUUID()); - const inFlight = useRef(false); - const pendingWindow = useRef(null); - const mounted = useRef(true); - useEffect(() => { - mounted.current = true; - return () => { - mounted.current = false; - pendingWindow.current?.close(); - pendingWindow.current = null; - }; - }, []); - const registration = endpoint.setup?.slackRegistration; - const uncertain = registration?.status === "uncertain"; - const creating = registration?.status === "creating"; - const created = Boolean(registration?.appId); - const configurationPending = registration?.errorCode === "slack_manifest_update_pending"; - - async function run(action: () => Promise) { - if (inFlight.current) return; - inFlight.current = true; - setBusy(true); - onBusy(true); - setError(null); - try { await action(); } - catch (failure) { setError(sanitizedSetupErrorMessage(failure, { configurationToken })); } - finally { - setConfigurationToken(""); - setBusy(false); - onBusy(false); - inFlight.current = false; - } - } - - async function create() { - // Reserve the window during the click so async app creation cannot trigger a popup blocker. - const installWindow = window.open("about:blank", "_blank"); - if (installWindow) { - installWindow.opener = null; - installWindow.document.title = "Preparing Slack installation"; - installWindow.document.body.textContent = "Preparing your Slack installation…"; - pendingWindow.current = installWindow; - } - let token = configurationToken.trim(); - setConfigurationToken(""); - try { - let next: ChatEndpoint; - try { - if (!created) await saveDetails(); - if (registration?.status === "failed" || checkedNoApp) requestId.current = crypto.randomUUID(); - next = await chatEndpointsApi.createSlackApp(endpoint.id, { - requestId: requestId.current, - credentials: { configurationToken: token }, - ...(checkedNoApp ? { confirmedNoAppCreated: true } : {}), - }); - } catch (failure) { - // A response can be lost after Slack creates the app. Only saved state can advance. - const current = await chatEndpointsApi.get(endpoint.id).catch(() => null); - if (current && mounted.current) onSaved(current); - if (!current?.setup?.slackRegistration?.appId) { - throw new Error(sanitizedSetupErrorMessage(failure, { configurationToken: token })); - } - next = current; - } - token = ""; - if (!mounted.current) return; - setCheckedNoApp(false); - onSaved(next); - const saved = next.setup?.slackRegistration; - if (!saved?.appId) return; - onContinue(); - // Pending/uncertain configuration and existing installations must not start OAuth. - if (saved.status !== "install" || saved.errorCode) return; - if (!installWindow || installWindow.closed) { - setError("Select Install in Slack to open the installation page."); - return; - } - const result = await chatEndpointsApi.installSlackApp(endpoint.id); - if (!mounted.current) return; - if (installWindow.closed) { - setError("Select Install in Slack to reopen the installation page."); - return; - } - installWindow.location.replace(result.authorizationUrl); - pendingWindow.current = null; - } finally { - token = ""; - pendingWindow.current?.close(); - pendingWindow.current = null; - } - } - - async function authorize() { - const result = await chatEndpointsApi.installSlackApp(endpoint.id); - // Current-tab navigation avoids popup blockers. The callback resumes this saved draft. - window.location.assign(result.authorizationUrl); - } - - return
- {error || registration?.errorCode ?

- {error ?? slackRegistrationErrorMessage(registration!.errorCode!)} -

: null} - {(stage === "app" && !created && !creating || configurationPending) && <> - {uncertain &&
- Open Slack app settings - -
} - {(!uncertain || checkedNoApp) &&
- -
-
    -
  1. In Slack app settings, find Your App Configuration Tokens and choose Generate Token.
  2. -
  3. Select the workspace where you want to install your bot.
  4. -
  5. Copy the Access Token and paste it below.
  6. -
-

Paperclip uses this token to create your app

-
- - setConfigurationToken(event.target.value)} - aria-describedby="slack-configuration-token-help" /> -
} - } - {creating &&

Creating your Slack app. You can return to this saved setup.

} - {created &&

- {endpoint.setup?.slackApp?.appName ?? "Your Slack app"} is created. - {stage === "credentials" && " Approve installation with your Slack account. Paperclip will link it to your signed-in Paperclip account and send you a welcome DM."} -

} - {stage === "credentials" && registration?.status === "credentials_saved" &&
-

Your installation credentials are saved. Retry connecting to finish setup. If Slack access has changed, authorize the same app again.

- -
} - {stage === "app" &&
- - -
} - - {configurationPending ? : stage === "app" ? : registration?.status === "credentials_saved" ? : } - -
; -} +export { OwnSlackAppSetup as SlackAutomaticSetup } from "./slack/OwnSlackAppSetup"; diff --git a/ui/src/pages/apps/chat/SlackSetupPrompt.tsx b/ui/src/pages/apps/chat/SlackSetupPrompt.tsx index b9256c27d59..2cc0568ea77 100644 --- a/ui/src/pages/apps/chat/SlackSetupPrompt.tsx +++ b/ui/src/pages/apps/chat/SlackSetupPrompt.tsx @@ -12,19 +12,21 @@ Ask me to take over for login, two-factor authentication, CAPTCHA, workspace-adm 2. Check public HTTPS before creating the app. Slack must be able to reach Paperclip's generated webhook URL over publicly reachable HTTPS with a valid certificate. Paperclip Cloud includes HTTPS. A private Tailscale Serve address alone is not reachable by Slack; self-hosting needs an authorized public ingress, such as Tailscale Funnel or a reverse proxy with a trusted certificate. Follow the warning's Learn more link if needed. Use the callback URL generated by this connection, including its full path. Check that it uses the intended public instance hostname or an explicitly configured webhook ingress, not localhost, an internal service address, or an old pool hostname. If it is wrong or unreachable, resolve the instance configuration before registration; do not invent a callback path, disable signature verification, or bypass Cloud authentication protections. -3. Create Slack app automatically. Review Slack app name, Bot display name, and Slash command under Advanced on Choose agent in Paperclip. Keep the generated defaults unless I requested other names. Use the automatic setup path for a new bot. Ask me to open Slack's Your App Configuration Tokens > Generate Token, select the agreed workspace, and enter the temporary configuration token directly into Paperclip's App configuration access token password field. Pause while I do that. Never read, copy, reveal, screenshot, or include that token in your prompt or tool output. Do not request its refresh token. Click Create Slack app after I enter it. Paperclip creates the app, saves its signing secret, and uploads the assigned agent's avatar. Avatar upload has no wizard step; if it fails, finish setup and optionally use Settings > Agent avatar for manual upload later. Do not recreate the app or request another configuration token for an optional avatar. Its saved state is the evidence of creation; a click or navigation is not proof. Resume the matching draft after refresh or Save & exit. If creation is uncertain, open Slack app settings and check the agreed workspace for the exact app before continuing. Never retry creation automatically. Only confirm that no app was created after I explicitly confirm that check, or recover the existing app on this same draft. No Slack CLI is required. +3. Use the saved Slack setup method. For a new connection, keep managed setup when it is the instance default. Continue on Choose agent opens Slack authorization or starts installation automatically; there is no separate Add to Slack screen. To explicitly use your own app, open Advanced on Choose agent and choose Use your own app below Slash command. Select only a workspace authorized by my own Paperclip account, or let me complete the manager authorization in Slack; pause for login and administrator consent. Reuse my saved workspace authorization for additional agents. Paperclip creates and installs the child app, uploads its avatar, and links my verified Slack identity. Continue within the same two-stop wizard: Choose agent, Connect. If Slack requires child-app consent or approval, complete that for the saved app and resume. Never switch methods after creation dispatch, migrate an existing bot, read manager credentials, or reuse a teammate's grant. If managed setup becomes unavailable, preserve the draft and report its recovery action. Use your own app only when selected or when managed setup is unavailable before creation. + +For the own-app path, create Slack app automatically. Review Slack app name, Bot display name, and Slash command under Advanced on Choose agent in Paperclip. Keep the generated defaults unless I requested other names. Use the automatic setup path for a new bot. Ask me to open Slack's Your App Configuration Tokens > Generate Token, select the agreed workspace, and enter the temporary configuration token directly into Paperclip's App configuration access token password field. Pause while I do that. Never read, copy, reveal, screenshot, or include that token in your prompt or tool output. Do not request its refresh token. Click Create Slack app after I enter it. Paperclip creates the app, saves its signing secret, and uploads the assigned agent's avatar. Avatar upload has no wizard step; if it fails, finish setup and optionally use Slack app settings > Basic Information to set its app icon later. Do not recreate the app or request another configuration token for an optional avatar. Its saved state is the evidence of creation; a click or navigation is not proof. Resume the matching draft after refresh or Save & exit. If creation is uncertain, open Slack app settings and check the agreed workspace for the exact app before continuing. Never retry creation automatically. Only confirm that no app was created after I explicitly confirm that check, or recover the existing app on this same draft. No Slack CLI is required. 4. Install Slack app. Successful creation and configuration advance Paperclip to Install Slack app and open Slack consent in a new window automatically. Use Install in Slack if that window was blocked or closed. Check the workspace and approve installation within the agreed scope. Pause for required login, ownership decisions, or administrator approval. Return to the saved wizard through Slack's OAuth redirect. Paperclip obtains and saves the bot token itself; do not locate, copy, or request the bot token or signing secret on the automatic path. Wait for Paperclip to report saved credentials. The fourth screen, Send a message to your agent, becomes Success automatically after Paperclip receives qualifying connection evidence. There is no separate Try it screen. If approval is declined or pending, preserve the app and resume its installation later without another configuration token. If authorization expires, start a fresh installation of this same app. If credentials are saved but connecting fails, use Retry connecting. If Slack access changed or the token was revoked, use Authorize in Slack again for this same app. Do not recreate the app. Installation automatically links the Slack account returned by OAuth to my signed-in Paperclip account and sends a welcome DM. Have me approve with my own Slack account; do not authorize with your account or a teammate’s account. There is no separate personal-linking step on the automatic path. Existing/conflicting/revoked links are managed in the connection’s Access settings later. Never overwrite them as a shortcut. Manual fallback only when selected or needed for recovery: choose Create manually or Use an existing app on the creation step. For manual creation, use Paperclip's generated manifest, select the agreed workspace in Slack, and install that exact app. Preserve all scopes, bot events, slash command, interactivity, and signed HTTP callbacks. For an existing app, reconcile its configuration with this connection's manifest and generated URLs. Have me enter the Bot User OAuth Token (OAuth & Permissions, xoxb- prefix) and Signing Secret (Basic Information > App Credentials) directly into the matching Paperclip fields, or use safe clipboard transfer only as described above. The Signing Secret is not the Client Secret, Verification Token, or app-level token. Click Connect Slack app. On a resumed manual setup, leave credential fields blank to keep saved values. Never use Socket Mode as a shortcut. -5. On the automatic path, use Send a message to your agent and its Open Slack link. If authorized, send a short message. Paperclip accepts an authenticated message event from the saved app/workspace as connection evidence and replaces this screen with Success. The welcome event may complete the check first. If Success appears, skip the Slack settings visit even if Slack's Request URL still says unverified. If Paperclip keeps waiting, expand Didn’t work? for Request URL instructions. On the manual path, open the same Slack app's Event Subscriptions. Make sure events are enabled. Beside the manifest's prefilled Request URL, click Retry if needed, wait for Slack to display Verified, and save changes when offered. Verification can fail before Paperclip has the credentials, so retry now. Return to Paperclip and wait for its verified state and automatic advancement, or Continue when available. If verification fails, inspect the specific error and the wizard's Troubleshooting URL: check public reachability, the correct app/workspace and signing secret, and the full generated callback URL. Also confirm the generated Slash Commands request URL and Interactivity & Shortcuts request URL were retained if those features fail. Use the manifest's values for each surface; do not assume verifying events proves every callback works. Do not recreate the app or change arbitrary URLs to hide a failed check. +5. On either provisioned path, use Send a message to your agent and its Open Slack link. If authorized, send a short message. Paperclip accepts an authenticated message event from the saved app/workspace as connection evidence and replaces this screen with Success. The welcome event may complete the check first. If Success appears, skip the Slack settings visit even if Slack's Request URL still says unverified. If Paperclip keeps waiting, expand Didn’t work? for Request URL instructions. On the manual path, open the same Slack app's Event Subscriptions. Make sure events are enabled. Beside the manifest's prefilled Request URL, click Retry if needed, wait for Slack to display Verified, and save changes when offered. Verification can fail before Paperclip has the credentials, so retry now. Return to Paperclip and wait for its verified state and automatic advancement, or Continue when available. If verification fails, inspect the specific error and the wizard's Troubleshooting URL: check public reachability, the correct app/workspace and signing secret, and the full generated callback URL. Also confirm the generated Slash Commands request URL and Interactivity & Shortcuts request URL were retained if those features fail. Use the manifest's values for each surface; do not assume verifying events proves every callback works. Do not recreate the app or change arbitrary URLs to hide a failed check. 6. Manual/existing-app paths only: connect your Slack account. Copy the exact command shown by Paperclip, consisting of this app's saved slash command followed by connect. In the correct Slack workspace, send it as my signed-in Slack user. This discovers identity without starting agent work. Return to Paperclip and wait for that identity to appear. Compare the Slack account/workspace and the target Paperclip user. Only select the ownership confirmation for that identity when they are verified as my accounts; its visible label is This is my Slack account and browser tools may identify it as Link to my Paperclip account. Ask me if ownership is ambiguous. Never link a teammate's account to mine. Confirm Linked to you and use Continue to message test. Keep unlinked-person access off unless I explicitly requested a different policy. On these manual paths installing the bot and linking a person are separate steps; future work uses that linked person's Paperclip permissions. 7. Prepare the agreed destination. If using a channel, invite/add this bot to that channel through Slack's UI if authorized. In the connection's Settings, inspect Where this agent can work. Use its Refresh action if available, or reload the page after the bot invitation if the channel is missing. Inspect Allowed Channels and make sure the intended test channel is enabled; newly invited channels start enabled, while an explicitly disabled channel stays off until a person re-enables it. Do not invite the bot to unrelated channels, enable every channel, or broaden anyone's access just to get a test to pass. For a DM, check the connection permits direct messages and use my conversation with this bot. Return to the saved wizard if you visited Settings. Reading access and allowed response destinations are distinct; seeing a channel in Slack does not prove the bot is allowed to reply there. -8. Optional live qualification when explicitly requested: on the automatic path, the wizard ends at Success and Done, with no additional test screen. Open the bot's DM directly if a conversation test was requested. The welcome DM and connection check alone do not prove an agent response. For a channel test or manual setup, send the wizard's suggested short message, for example @ you there? Find the bot belonging to this Slack app in Slack's mention suggestions and select it. Slack's actual display name can differ from Paperclip's suggested username: for example, Paperclip may suggest @cedarqa while Slack shows cedar-qa. Preserve Slack's displayed punctuation and select the real bot; pasting plain text that resembles a mention may not notify it. Use the configured bot, not a literal example. For a channel test, use a mention rather than the slash connect command. Wait for a real response in the originating Slack thread. Open the connection's Conversations page, follow the matching Open task link, and verify that the assigned agent's run completed successfully; the agent's Runs page provides the execution result if the Slack reply has no task link. Send one short follow-up in the same thread without another mention and confirm continuity and a single delivered answer. Do not claim success from a typing indicator, a verified webhook, the wizard's checkmark, or a manually posted substitute reply. If a run stops or no answer arrives, inspect the task/run and connection Activity, distinguish credential/callback/destination/identity failures from an agent-runtime failure, and resolve the specific issue or report the remaining blocker without exposing secrets. +8. Optional live qualification when explicitly requested: on either provisioned path, the wizard ends at Success and Done, with no additional test screen. Open the bot's DM directly if a conversation test was requested. The welcome DM and connection check alone do not prove an agent response. For a channel test or manual setup, send the wizard's suggested short message, for example @ you there? Find the bot belonging to this Slack app in Slack's mention suggestions and select it. Slack's actual display name can differ from Paperclip's suggested username: for example, Paperclip may suggest @cedarqa while Slack shows cedar-qa. Preserve Slack's displayed punctuation and select the real bot; pasting plain text that resembles a mention may not notify it. Use the configured bot, not a literal example. For a channel test, use a mention rather than the slash connect command. Wait for a real response in the originating Slack thread. Open the connection's Conversations page, follow the matching Open task link, and verify that the assigned agent's run completed successfully; the agent's Runs page provides the execution result if the Slack reply has no task link. Send one short follow-up in the same thread without another mention and confirm continuity and a single delivered answer. Do not claim success from a typing indicator, a verified webhook, the wizard's checkmark, or a manually posted substitute reply. If a run stops or no answer arrives, inspect the task/run and connection Activity, distinguish credential/callback/destination/identity failures from an agent-runtime failure, and resolve the specific issue or report the remaining blocker without exposing secrets. 9. Finish and review. Use Done to finish setup. The message test is optional and does not waive required credentials, callback verification, or identity linking. If I declined testing, finish without claiming it passed. Confirm the connection is saved for the correct agent/workspace, inspect its permitted destinations and linked identity, and leave existing access restrictions and per-action approvals intact. Slack tools for this agent's eligible tasks and routines use this bot connection and the responsible user's linked Slack account; do not create a separate personal Slack MCP connection or promise all methods work regardless of scopes or plan. Additional communication instructions are optional and apply to new tasks; add them only if I supplied preferences. Other people link their own accounts using the Access page's connect instructions; they do not share the bot secrets or my identity. diff --git a/ui/src/pages/apps/chat/SlackTestMessageButton.tsx b/ui/src/pages/apps/chat/SlackTestMessageButton.tsx new file mode 100644 index 00000000000..10cea6448d2 --- /dev/null +++ b/ui/src/pages/apps/chat/SlackTestMessageButton.tsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Check, Loader2, Send } from "lucide-react"; +import type { SlackTestMessageResult } from "@paperclipai/shared"; +import { chatEndpointsApi } from "@/api/chatEndpoints"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { queryKeys } from "@/lib/queryKeys"; + +export function SlackTestMessageButton({ endpointId }: { endpointId: string }) { + const queryClient = useQueryClient(); + const [requestId, setRequestId] = useState(() => crypto.randomUUID()); + const [accounts, setAccounts] = useState["accounts"] | null>(null); + const [nextCursor, setNextCursor] = useState(null); + const [selected, setSelected] = useState(""); + const [invitationExpired, setInvitationExpired] = useState(false); + const [search, setSearch] = useState(""); + const send = useMutation({ + mutationFn: (input: { slackUserId?: string; accountCursor?: string; requestId?: string }) => chatEndpointsApi.sendSlackTestMessage(endpointId, { requestId, ...input }), + onSuccess: (result, input) => { + if (result.status === "connection_required") { + setAccounts(previous => input.accountCursor ? [...new Map([...(previous ?? []), ...result.accounts].map(account => [account.id, account])).values()] : result.accounts); + setNextCursor(result.nextCursor); + return; + } + setAccounts(null); + void queryClient.invalidateQueries({ queryKey: queryKeys.chatEndpoints.principals(endpointId) }); + }, + }); + const sent = send.data?.status === "sent"; + const pendingConnection = send.data?.status === "connection_pending" ? send.data : null; + useEffect(() => { + setInvitationExpired(false); + if (!pendingConnection) return; + const timer = setTimeout(() => setInvitationExpired(true), Math.max(0, new Date(pendingConnection.expiresAt).getTime() - Date.now())); + return () => clearTimeout(timer); + }, [pendingConnection?.expiresAt]); + const principals = useQuery({ + queryKey: queryKeys.chatEndpoints.principals(endpointId), + queryFn: () => chatEndpointsApi.listPrincipals(endpointId), + enabled: Boolean(pendingConnection) && !invitationExpired, refetchInterval: pendingConnection && !invitationExpired ? 3000 : false, + }); + const confirmed = Boolean(pendingConnection && principals.data?.some(link => link.principalId === pendingConnection.principalId && link.status === "linked")); + useEffect(() => { + if (confirmed && !send.isPending && !send.isError) send.mutate({ slackUserId: selected }); + }, [confirmed, send.isPending, send.isError]); + const reset = () => { setRequestId(crypto.randomUUID()); send.reset(); }; + const startTest = () => { + if (invitationExpired) { + const next = crypto.randomUUID(); setRequestId(next); + send.mutate({ requestId: next, slackUserId: selected }); + } else send.mutate({}); + }; + return
+ + {pendingConnection &&

{invitationExpired ? "The Slack invitation expired." : "Open the invitation in Slack to connect your account."}

} + {send.isError &&
+ {send.error.message} +
} + { if (!open && !send.isPending) setAccounts(null); }}> + + + Connect your Slack account + Choose your account. Messages to this agent will use your Paperclip permissions. + + setSearch(event.target.value)} /> +
+ {accounts?.filter(account => `${account.name} ${account.id}`.toLowerCase().includes(search.toLowerCase())).map(account => + )} + {accounts?.length === 0 &&

No available accounts found in this Slack workspace.

} +
+ {nextCursor && } + {send.isError &&

{send.error.message}

} +
+ + +
+
+
+
; +} diff --git a/ui/src/pages/apps/chat/SlackToolSettings.tsx b/ui/src/pages/apps/chat/SlackToolSettings.tsx index 8e2fb86df9b..772eb0af66c 100644 --- a/ui/src/pages/apps/chat/SlackToolSettings.tsx +++ b/ui/src/pages/apps/chat/SlackToolSettings.tsx @@ -1,4 +1,3 @@ -import { Link } from "react-router-dom"; import { useId, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import type { @@ -200,11 +199,9 @@ export function SlackSearchView({ export function SlackToolsSettings({ companyId, endpointId, - connectionId, }: { companyId: string; endpointId: string; - connectionId?: string | null; }) { const query = useQuery({ queryKey: ["slack-capabilities", companyId, endpointId], @@ -217,14 +214,7 @@ export function SlackToolsSettings({ capabilities={query.data} error={query.error?.message} /> - {connectionId && ( - - Manage action permissions - - )} +
); } diff --git a/ui/src/pages/apps/chat/chat-ui-contract.test.ts b/ui/src/pages/apps/chat/chat-ui-contract.test.ts index f67ccd5be70..34b79e374d4 100644 --- a/ui/src/pages/apps/chat/chat-ui-contract.test.ts +++ b/ui/src/pages/apps/chat/chat-ui-contract.test.ts @@ -242,7 +242,8 @@ describe("chat connector UI contract", () => { expect(setup).toContain("defaultSlackAppConfiguration"); expect(setup).not.toContain("No credentials"); expect(setup).not.toContain("managed Microsoft app"); - expect(setup).toContain("endpoint.providerAccountId && !repairing"); + expect(setup).toContain("slackSetupState({ endpoint, repairing"); + expect(source("./slack/setup-state.ts")).toContain("endpoint.providerAccountId && !repairing"); expect(setup).not.toContain('field("webhookSecret"'); expect(setup).not.toContain("@paperclipai/teams-connect"); expect(setup).not.toContain("Copy setup command"); diff --git a/ui/src/pages/apps/chat/slack/ManagedSlackSetup.test.tsx b/ui/src/pages/apps/chat/slack/ManagedSlackSetup.test.tsx new file mode 100644 index 00000000000..245773a6a70 --- /dev/null +++ b/ui/src/pages/apps/chat/slack/ManagedSlackSetup.test.tsx @@ -0,0 +1,88 @@ +// @vitest-environment jsdom +import { StrictMode, act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { beforeEach, afterEach, expect, it, vi } from "vitest"; +import type { ChatEndpoint } from "@/api/chatEndpoints"; +import { ManagedSlackSetup } from "./ManagedSlackSetup"; + +const api = vi.hoisted(() => ({ slackSetupOptions: vi.fn(), provisionManagedSlack: vi.fn(), authorizeSlackManager: vi.fn(), get: vi.fn() })); +vi.mock("@/api/chatEndpoints", () => ({ chatEndpointsApi: api })); +// Stop at the navigation boundary without leaving jsdom. +const navigation = vi.hoisted(() => vi.fn()); +vi.mock("@/lib/oauthHandoff", () => ({ prepareOAuthNavigation: navigation, savePendingCloudHandoff: vi.fn() })); +let root: Root; +let container: HTMLDivElement; +let client: QueryClient; +let endpoint: ChatEndpoint; +const props = () => ({ endpoint, stage: "app" as const, disabled: false, autoStart: true, saveDetails: vi.fn().mockResolvedValue(undefined), onSaved: vi.fn(), onBusy: vi.fn(), onSaveExit: vi.fn(), onManual: vi.fn(), onContinue: vi.fn() }); +beforeEach(() => { + vi.resetAllMocks(); + container = document.createElement("div"); document.body.append(container); root = createRoot(container); + client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + endpoint = { id: "endpoint", companyId: "company", assignedAgentName: "Maya", setup: { slackSetupMethod: "managed" } } as ChatEndpoint; + api.slackSetupOptions.mockResolvedValue({ managedAvailable: true, workspaces: [{ grantId: "grant", workspaceName: "Paperclip" }] }); + api.provisionManagedSlack.mockResolvedValue({}); api.get.mockResolvedValue(endpoint); + api.authorizeSlackManager.mockResolvedValue({ authorizationUrl: "https://slack.com/oauth" }); + navigation.mockImplementation(() => new Promise(() => {})); +}); +afterEach(async () => { await act(async () => root.unmount()); client.clear(); container.remove(); }); +async function render(overrides = {}) { + await act(async () => { root.render(); }); + await act(async () => { await new Promise(resolve => setTimeout(resolve, 30)); }); +} +it("starts a single managed install after Continue even across rerenders", async () => { + await render(); await render(); + expect(api.provisionManagedSlack).toHaveBeenCalledTimes(1); + expect(api.provisionManagedSlack).toHaveBeenCalledWith("endpoint", expect.objectContaining({ grantId: "grant" })); + expect(api.authorizeSlackManager).not.toHaveBeenCalled(); +}); +it("requests explicit reinstall when reconnecting a configured app", async () => { + endpoint.setup!.slackRegistration = { status: "configured", appId: "app", managerGrantId: "grant" } as NonNullable["slackRegistration"]; + await render({ autoStart: false, reconnect: true }); + const button = Array.from(container.querySelectorAll("button")).find(item => item.textContent === "Continue in Slack"); + expect(button).toBeDefined(); + await act(async () => button!.click()); + expect(api.provisionManagedSlack).toHaveBeenCalledWith("endpoint", expect.objectContaining({ grantId: "grant", reconnect: true, requestId: expect.any(String) })); +}); +it("reuses the reconnect receipt after a lost response and failed recovery read", async () => { + endpoint.setup!.slackRegistration = { status: "configured", appId: "app", managerGrantId: "grant" } as NonNullable["slackRegistration"]; + api.provisionManagedSlack.mockRejectedValue(new Error("Response lost")); + api.get.mockRejectedValue(new Error("Recovery read unavailable")); + await render({ autoStart: false, reconnect: true }); + const button = () => Array.from(container.querySelectorAll("button")).find(item => item.textContent === "Continue in Slack")!; + await act(async () => button().click()); + await act(async () => button().click()); + expect(api.provisionManagedSlack).toHaveBeenCalledTimes(2); + expect(api.provisionManagedSlack.mock.calls[1]![1]).toEqual(api.provisionManagedSlack.mock.calls[0]![1]); +}); +it("opens manager authorization directly when there is no saved workspace", async () => { + api.slackSetupOptions.mockResolvedValue({ managedAvailable: true, workspaces: [] }); + await render(); + expect(api.authorizeSlackManager).toHaveBeenCalledTimes(1); + expect(navigation).toHaveBeenCalledWith({ authorizationUrl: "https://slack.com/oauth" }); + expect(api.provisionManagedSlack).not.toHaveBeenCalled(); +}); +it("does not start while Choose agent is visible or after Save & exit", async () => { + await render({ autoStart: false }); + expect(api.provisionManagedSlack).not.toHaveBeenCalled(); + expect(api.authorizeSlackManager).not.toHaveBeenCalled(); +}); +it("waits for an explicit workspace choice when there are several", async () => { + api.slackSetupOptions.mockResolvedValue({ managedAvailable: true, workspaces: [{ grantId: "one", workspaceName: "One" }, { grantId: "two", workspaceName: "Two" }] }); + await render(); expect(api.provisionManagedSlack).not.toHaveBeenCalled(); + expect(container.textContent).toContain("Choose a Slack workspace"); +}); +it.each(["uncertain", "failed", "creating", "install", "credentials_saved"])("does not automatically retry a %s registration", async status => { + endpoint.setup!.slackRegistration = { status } as NonNullable["slackRegistration"]; + await render(); expect(api.provisionManagedSlack).not.toHaveBeenCalled(); expect(api.authorizeSlackManager).not.toHaveBeenCalled(); +}); +it("does not loop authorization after denial", async () => { + endpoint.setup!.slackManagerError = "slack_install_declined"; + await render(); expect(api.authorizeSlackManager).not.toHaveBeenCalled(); expect(api.provisionManagedSlack).not.toHaveBeenCalled(); +}); +it("does not silently retry an ambiguous request after refresh of setup options", async () => { + api.provisionManagedSlack.mockRejectedValue(new Error("Request failed")); + await render(); await render(); + expect(api.provisionManagedSlack).toHaveBeenCalledTimes(1); +}); diff --git a/ui/src/pages/apps/chat/slack/ManagedSlackSetup.tsx b/ui/src/pages/apps/chat/slack/ManagedSlackSetup.tsx new file mode 100644 index 00000000000..d9023162f02 --- /dev/null +++ b/ui/src/pages/apps/chat/slack/ManagedSlackSetup.tsx @@ -0,0 +1,92 @@ +import { useEffect, useRef, useState, type ComponentProps } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Loader2 } from "lucide-react"; +import { slackRegistrationErrorMessage } from "@paperclipai/shared"; +import { chatEndpointsApi } from "@/api/chatEndpoints"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { SetupWizardFooter } from "@/components/SetupWizard"; +import type { OwnSlackAppSetup } from "./OwnSlackAppSetup"; +import { prepareOAuthNavigation, savePendingCloudHandoff } from "@/lib/oauthHandoff"; +import { sanitizedSetupErrorMessage } from "../chat-setup-error"; + +export function ManagedSlackSetup({ endpoint, disabled, saveDetails, onSaved, onBusy, onSaveExit, autoStart = false, reconnect = false }: ComponentProps & { autoStart?: boolean; reconnect?: boolean }) { + const choices = useQuery({ queryKey: ["slack-managed-workspaces", endpoint.companyId], queryFn: () => chatEndpointsApi.slackSetupOptions(endpoint.companyId), retry: false }); + const [selectedGrant, setSelectedGrant] = useState(null); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [confirmed, setConfirmed] = useState(false); + const inFlight = useRef(false); + const requestId = useRef(crypto.randomUUID()); + const registration = endpoint.setup?.slackRegistration; + const started = Boolean(registration); + const workspaces = choices.data?.workspaces ?? []; + const grantId = registration?.managerGrantId ?? selectedGrant ?? (workspaces.length === 1 ? workspaces[0]!.grantId : null); + const needsAuthorization = !grantId || registration?.errorCode === "slack_manager_reauthorize" || Boolean(grantId && !workspaces.some(workspace => workspace.grantId === grantId)); + const uncertain = registration?.status === "uncertain"; + const creating = registration?.status === "creating"; + async function run(action: () => Promise) { + if (inFlight.current) return; + inFlight.current = true; setBusy(true); onBusy(true); setError(null); + try { await action(); } + catch (failure) { setError(sanitizedSetupErrorMessage(failure, {})); await Promise.allSettled([choices.refetch(), chatEndpointsApi.get(endpoint.id).then(onSaved)]); } + finally { inFlight.current = false; setBusy(false); onBusy(false); } + } + async function authorize() { + const authorization = await chatEndpointsApi.authorizeSlackManager(endpoint.id); + const target = await prepareOAuthNavigation(authorization); + if (target.kind === "reauthentication" && authorization.handoff) savePendingCloudHandoff(authorization.handoff.session); + window.location.assign(target.url); + } + async function connect() { + if (needsAuthorization || !grantId) { + await authorize(); return; + } + if (!started) await saveDetails(); + if (confirmed || registration?.status === "failed") requestId.current = crypto.randomUUID(); + const input = { requestId: requestId.current, grantId, ...(reconnect ? { reconnect: true } : {}), ...(confirmed ? { confirmedNoAppCreated: true } : {}) }; + setConfirmed(false); + const result = await chatEndpointsApi.provisionManagedSlack(endpoint.id, input); + await choices.refetch(); + onSaved(await chatEndpointsApi.get(endpoint.id)); + if (result.authorization) window.location.assign(result.authorization.authorizationUrl); + } + const autoStarted = useRef(false); + useEffect(() => { + // Only fresh setup can start itself. Declines, uncertain creation, and saved + // registration recovery require an explicit action and never recreate an app. + if (!autoStart || autoStarted.current || disabled || !choices.data?.managedAvailable + || choices.isFetching || choices.isError || started || endpoint.setup?.slackManagerError + || workspaces.length > 1) return; + autoStarted.current = true; + void run(connect); + }); + const setupError = (code: string) => code === "slack_manifest_update_pending" + ? "Your app was created. Retry to finish its configuration." + : code === "slack_creation_uncertain" ? "Slack may have created your app. Check Slack app settings before trying again." + : slackRegistrationErrorMessage(code); + const message = error ?? (endpoint.setup?.slackManagerError ? setupError(endpoint.setup.slackManagerError) : null) ?? (choices.isError ? "Couldn't load your Slack workspaces. Try again." : registration?.errorCode ? setupError(registration.errorCode) : null); + return
+ {message &&

{message}

} + {busy || choices.isPending || creating ?

{busy ? needsAuthorization ? "Opening Slack…" : "Connecting your agent…" : creating ? "Setting up your Slack app…" : "Loading Slack…"}

+ : !choices.data?.managedAvailable ?

Managed Slack setup is unavailable. Try again later.

+ : <> + {workspaces.length === 1 &&

Add {endpoint.assignedAgentName} to {workspaces[0]!.workspaceName}.

} + {workspaces.length > 1 && } + {(!started || registration?.errorCode === "slack_manager_reauthorize") && workspaces.length > 0 &&
} + } + {uncertain &&
+ Open Slack app settings + +
} + + {choices.isError || !choices.isPending && !choices.data?.managedAvailable ? : } + +
; +} diff --git a/ui/src/pages/apps/chat/slack/OwnSlackAppSetup.tsx b/ui/src/pages/apps/chat/slack/OwnSlackAppSetup.tsx new file mode 100644 index 00000000000..a08c09376b7 --- /dev/null +++ b/ui/src/pages/apps/chat/slack/OwnSlackAppSetup.tsx @@ -0,0 +1,185 @@ +import { useEffect, useRef, useState } from "react"; +import { ExternalLink, Loader2 } from "lucide-react"; +import { slackRegistrationErrorMessage } from "@paperclipai/shared"; +import { chatEndpointsApi, type ChatEndpoint } from "@/api/chatEndpoints"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import { SetupWizardFooter } from "@/components/SetupWizard"; +import { sanitizedSetupErrorMessage } from "../chat-setup-error"; +import { SlackSetupAdvanced } from "../SlackAppDetails"; + +/** Uses ordinary request-local state: configuration tokens never enter a query/mutation cache. */ +export function OwnSlackAppSetup({ endpoint, stage, disabled, saveDetails, onSaved, onBusy, + onManual, onManaged, onContinue, onSaveExit }: { + endpoint: ChatEndpoint; + stage: "app" | "credentials"; + disabled: boolean; + saveDetails: () => Promise; + onSaved: (endpoint: ChatEndpoint) => void; + onBusy: (busy: boolean) => void; + onManual: (existing: boolean) => Promise; + onManaged?: () => Promise; + onContinue: () => void; + onSaveExit: () => void; +}) { + const [configurationToken, setConfigurationToken] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [checkedNoApp, setCheckedNoApp] = useState(false); + const requestId = useRef(crypto.randomUUID()); + const inFlight = useRef(false); + const pendingWindow = useRef(null); + const mounted = useRef(true); + useEffect(() => { + mounted.current = true; + return () => { + mounted.current = false; + pendingWindow.current?.close(); + pendingWindow.current = null; + }; + }, []); + const registration = endpoint.setup?.slackRegistration; + const uncertain = registration?.status === "uncertain"; + const creating = registration?.status === "creating"; + const created = Boolean(registration?.appId); + const configurationPending = registration?.errorCode === "slack_manifest_update_pending"; + + async function run(action: () => Promise) { + if (inFlight.current) return; + inFlight.current = true; + setBusy(true); + onBusy(true); + setError(null); + try { await action(); } + catch (failure) { setError(sanitizedSetupErrorMessage(failure, { configurationToken })); } + finally { + setConfigurationToken(""); + setBusy(false); + onBusy(false); + inFlight.current = false; + } + } + + async function create() { + // Reserve the window during the click so async app creation cannot trigger a popup blocker. + const installWindow = window.open("about:blank", "_blank"); + if (installWindow) { + installWindow.opener = null; + installWindow.document.title = "Preparing Slack installation"; + installWindow.document.body.textContent = "Preparing your Slack installation…"; + pendingWindow.current = installWindow; + } + let token = configurationToken.trim(); + setConfigurationToken(""); + try { + let next: ChatEndpoint; + try { + if (!created) await saveDetails(); + if (registration?.status === "failed" || checkedNoApp) requestId.current = crypto.randomUUID(); + setCheckedNoApp(false); + next = await chatEndpointsApi.createSlackApp(endpoint.id, { + requestId: requestId.current, + credentials: { configurationToken: token }, + ...(checkedNoApp ? { confirmedNoAppCreated: true } : {}), + }); + } catch (failure) { + // A response can be lost after Slack creates the app. Only saved state can advance. + const current = await chatEndpointsApi.get(endpoint.id).catch(() => null); + if (current && mounted.current) onSaved(current); + if (!current?.setup?.slackRegistration?.appId) { + throw new Error(sanitizedSetupErrorMessage(failure, { configurationToken: token })); + } + next = current; + } + token = ""; + if (!mounted.current) return; + onSaved(next); + const saved = next.setup?.slackRegistration; + if (!saved?.appId) return; + onContinue(); + // Pending/uncertain configuration and existing installations must not start OAuth. + if (saved.status !== "install" || saved.errorCode) return; + if (!installWindow || installWindow.closed) { + setError("Select Install in Slack to open the installation page."); + return; + } + const result = await chatEndpointsApi.installSlackApp(endpoint.id); + if (!mounted.current) return; + if (installWindow.closed) { + setError("Select Install in Slack to reopen the installation page."); + return; + } + installWindow.location.replace(result.authorizationUrl); + pendingWindow.current = null; + } finally { + token = ""; + pendingWindow.current?.close(); + pendingWindow.current = null; + } + } + + async function authorize() { + const result = await chatEndpointsApi.installSlackApp(endpoint.id); + // Current-tab navigation avoids popup blockers. The callback resumes this saved draft. + window.location.assign(result.authorizationUrl); + } + + return
+ {error || registration?.errorCode ?

+ {error ?? slackRegistrationErrorMessage(registration!.errorCode!)} +

: null} + {(stage === "app" && !created && !creating || configurationPending) && <> + {uncertain &&
+ Open Slack app settings + +
} + {(!uncertain || checkedNoApp) &&
+ +
+
    +
  1. In Slack app settings, find Your App Configuration Tokens and choose Generate Token.
  2. +
  3. Select the workspace where you want to install your bot.
  4. +
  5. Copy the Access Token and paste it below.
  6. +
+

Paperclip uses this token to create your app

+
+ + setConfigurationToken(event.target.value)} + aria-describedby="slack-configuration-token-help" /> +
} + } + {creating &&

Creating your Slack app. You can return to this saved setup.

} + {created &&

+ {endpoint.setup?.slackApp?.appName ?? "Your Slack app"} is created. + {stage === "credentials" && " Approve installation with your Slack account. Paperclip will link it to your signed-in Paperclip account and send you a welcome DM."} +

} + {stage === "credentials" && registration?.status === "credentials_saved" &&
+

Your installation credentials are saved. Retry connecting to finish setup. If Slack access has changed, authorize the same app again.

+ +
} + {stage === "app" &&
+ {onManaged && !registration && } + + +
} + + {configurationPending ? : stage === "app" ? : registration?.status === "credentials_saved" ? : } + +
; +} diff --git a/ui/src/pages/apps/chat/slack/SlackSetup.tsx b/ui/src/pages/apps/chat/slack/SlackSetup.tsx new file mode 100644 index 00000000000..e962d6d7cde --- /dev/null +++ b/ui/src/pages/apps/chat/slack/SlackSetup.tsx @@ -0,0 +1,11 @@ +import { useQuery } from "@tanstack/react-query"; +import type { ComponentProps } from "react"; +import { chatEndpointsApi } from "@/api/chatEndpoints"; +import { OwnSlackAppSetup } from "./OwnSlackAppSetup"; +import { ManagedSlackSetup } from "./ManagedSlackSetup"; +export function SlackSetup(props: ComponentProps & { autoStart?: boolean; reconnect?: boolean }) { + const choices = useQuery({ queryKey: ["slack-managed-workspaces", props.endpoint.companyId], queryFn: () => chatEndpointsApi.slackSetupOptions(props.endpoint.companyId), retry: false }); + return props.endpoint.setup?.slackSetupMethod === "managed" + ? + : props.onSaved(await chatEndpointsApi.update(props.endpoint.id, { slackSetupMethod: "managed" })) : undefined} />; +} diff --git a/ui/src/pages/apps/chat/slack/setup-state.test.ts b/ui/src/pages/apps/chat/slack/setup-state.test.ts new file mode 100644 index 00000000000..b21830596b1 --- /dev/null +++ b/ui/src/pages/apps/chat/slack/setup-state.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import type { ChatEndpoint } from "@/api/chatEndpoints"; +import { slackSetupState } from "./setup-state"; +const draft = (method?: "managed" | "automatic" | "manual" | "existing") => ({ id: "endpoint", setup: { step: "provider_setup", slackSetupMethod: method } }) as ChatEndpoint; +const state = (endpoint: ChatEndpoint | null, extra = {}) => slackSetupState({ endpoint, repairing: false, credentialsReady: false, identityReady: false, ...extra }); +describe("Slack setup capabilities and progress", () => { + it("does not flash token instructions while discovery is pending", () => { + expect(state(null).labels).toEqual(["Choose agent", "Connect"]); + }); + it("defaults only new drafts and preserves legacy manual drafts", () => { + expect(state(null, { defaultMethod: "managed" }).labels).toEqual(["Choose agent", "Connect"]); + expect(state(draft(), { defaultMethod: "managed" }).provisioned).toBe(false); + expect(state(draft("automatic"), { defaultMethod: "managed" }).labels).toHaveLength(4); + }); + it("keeps managed provider operations within the two visible stops", () => { + const endpoint = draft("managed"); + endpoint.setup!.slackRegistration = { status: "install", appId: "AAPP", managementUrl: "https://api.slack.com/apps/AAPP" }; + expect(state(endpoint).toVisible(state(endpoint).availableStage)).toBe(1); + endpoint.setup!.slackRegistration.status = "credentials_saved"; + expect(state(endpoint).toVisible(state(endpoint).availableStage)).toBe(1); + endpoint.providerAccountId = "TWORKSPACE"; + endpoint.setup!.slackRegistration.status = "configured"; + endpoint.setup!.slackAccount = { status: "linked", externalUserId: "UPERSON", paperclipUserId: "person", welcomeStatus: "sent" }; + expect(state(endpoint).toVisible(state(endpoint).availableStage)).toBe(1); + endpoint.setup!.step = "test"; + expect(state(endpoint).complete).toBe(true); + expect(state(endpoint).toStage(1)).toBe(3); + expect(state(endpoint).toVisible(state(endpoint).availableStage)).toBe(1); + }); +}); diff --git a/ui/src/pages/apps/chat/slack/setup-state.ts b/ui/src/pages/apps/chat/slack/setup-state.ts new file mode 100644 index 00000000000..9e25bc230db --- /dev/null +++ b/ui/src/pages/apps/chat/slack/setup-state.ts @@ -0,0 +1,30 @@ +import type { ChatEndpoint } from "@/api/chatEndpoints"; +import { isProvisionedSlackSetup } from "@paperclipai/shared"; +export { isProvisionedSlackSetup }; +export function slackSetupNavigation(method: string | undefined, availableStage = 1) { + const managed = method === "managed"; + return { + labels: managed ? ["Choose agent", "Connect"] : isProvisionedSlackSetup(method) + ? ["Choose agent", "App configuration access token", "Install Slack app", "Send a message"] + : ["Choose agent", "Create Slack app", "Add credentials", "Verify Slack connection", "Connect your Slack account", "Try it"], + toVisible: (step: number) => managed ? Math.min(step, 1) : step, + toStage: (step: number) => managed && step === 1 ? availableStage : step, + }; +} + +/** Logical stages stay compatible with existing draft URLs; navigation is derived here. */ +export function slackSetupState(input: { endpoint: ChatEndpoint | null; repairing: boolean; credentialsReady: boolean; identityReady: boolean; defaultMethod?: string }) { + const { endpoint, repairing, credentialsReady, identityReady } = input; + const method = endpoint ? endpoint.setup?.slackSetupMethod : input.defaultMethod ?? "managed"; + const provisioned = isProvisionedSlackSetup(method); + const complete = provisioned && (endpoint?.setup?.step === "test" || endpoint?.setup?.step === "complete"); + const finalStage = provisioned ? 3 : 5; + const availableStage = !endpoint ? 0 + : provisioned && endpoint.setup?.slackRegistration?.status === "configured" && endpoint.setup.slackAccount?.status !== "linked" && endpoint.setup.step !== "complete" ? 2 + : !repairing && (endpoint.setup?.step === "test" || endpoint.setup?.step === "complete") + ? endpoint.setup.step !== "complete" && !provisioned && !identityReady ? 4 : finalStage + : provisioned && endpoint.setup?.slackRegistration?.status === "credentials_saved" ? 2 + : endpoint.providerAccountId && !repairing ? 3 + : credentialsReady || repairing || method === "existing" || provisioned && endpoint.setup?.slackRegistration?.appId ? 2 : 1; + return { ...slackSetupNavigation(method, availableStage), provisioned, complete, finalStage, availableStage }; +} diff --git a/ui/storybook/fixtures/SlackSetupFixture.tsx b/ui/storybook/fixtures/SlackSetupFixture.tsx index be7b266de05..dc1d26a3991 100644 --- a/ui/storybook/fixtures/SlackSetupFixture.tsx +++ b/ui/storybook/fixtures/SlackSetupFixture.tsx @@ -7,10 +7,10 @@ import { type ChatEndpoint } from "@/api/chatEndpoints"; import { storybookAgents, storybookAuthSession } from "./paperclipData"; import { defaultSlackAppConfiguration } from "@paperclipai/shared"; -export type SlackSetupScenario = "choose" | "create" | "manual" | "install" | "declined" | "uncertain" | "manifest_pending" | "recovery" | "verify" | "avatar_failed" | "welcome_failed" | "success"; +export type SlackSetupScenario = "choose" | "create" | "manual" | "install" | "declined" | "approval_denied" | "uncertain" | "manifest_pending" | "recovery" | "verify" | "avatar_failed" | "welcome_failed" | "success"; /** Production wizard over a local provider fixture. No requests go to Slack. */ -export function SlackSetupFixture({ scenario = "create" }: { scenario?: SlackSetupScenario }) { +export function SlackSetupFixture({ scenario = "create", managed = false, managedAvailable = true, workspaceCount = 1, optionsDelayMs = 0, optionsError = false }: { scenario?: SlackSetupScenario; managed?: boolean; managedAvailable?: boolean; workspaceCount?: number; optionsDelayMs?: number; optionsError?: boolean }) { const [ready, setReady] = useState(false); const [signalVerification, setSignalVerification] = useState<() => void>(() => () => {}); useLayoutEffect(() => { @@ -20,14 +20,16 @@ export function SlackSetupFixture({ scenario = "create" }: { scenario?: SlackSet const endpoint: ChatEndpoint = { id: "slack-story", companyId: "company-storybook", provider: "slack", status: "draft", assignedAgentId: agent.id, assignedAgentName: agent.name, allowUnlinkedPeople: false, - setup: { step: "provider_setup", slackSetupMethod: scenario === "manual" ? "manual" : "automatic", + setup: { step: "provider_setup", slackSetupMethod: scenario === "manual" ? "manual" : managed ? "managed" : "automatic", slackApp: defaultSlackAppConfiguration(agent.name), webhookUrl: "https://ingress.example/api/chat-webhooks/slack-story/slack", slackOAuthCallbackUri: "https://board.example/api/chat-slack/oauth/callback", }, }; + const grantId = "11111111-1111-4111-8111-111111111111"; + let authorized = workspaceCount > 0 || new URLSearchParams(window.location.search).get("slackFixtureAuthorized") === "1"; const created = () => { - endpoint.setup!.slackRegistration = { status: "install", appId: "ASTORY", managementUrl: "https://api.slack.com/apps/ASTORY" }; + endpoint.setup!.slackRegistration = { status: "install", ...(managed ? { managerGrantId: grantId } : {}), appId: "ASTORY", managementUrl: "https://api.slack.com/apps/ASTORY" }; endpoint.setup!.slackAvatar = scenario === "avatar_failed" ? { status: "failed", errorCode: "slack_avatar_upload_failed" } : { status: "uploaded", uploadedAt: new Date().toISOString() }; }; let linked = false; @@ -37,11 +39,12 @@ export function SlackSetupFixture({ scenario = "create" }: { scenario?: SlackSet endpoint.setup!.slackAccount = { externalUserId: "UPERSON", paperclipUserId: storybookAuthSession.user.id, status: "linked", welcomeStatus: scenario === "welcome_failed" ? "failed" : "sent", ...(scenario === "welcome_failed" ? {} : { dmChannelId: "DSTORY" }) }; endpoint.status = "verifying"; endpoint.providerAccountId = "TSTORY"; endpoint.botExternalId = "USTORY"; endpoint.botUsername = "maya"; }; - if (["install", "declined", "recovery", "manifest_pending"].includes(scenario)) created(); + if (["install", "declined", "approval_denied", "recovery", "manifest_pending"].includes(scenario)) created(); if (scenario === "manifest_pending") endpoint.setup!.slackRegistration!.errorCode = "slack_manifest_update_pending"; - if (scenario === "declined") endpoint.setup!.slackRegistration!.errorCode = "slack_install_declined"; + if (scenario === "declined") endpoint.setup!.slackRegistration!.errorCode = managed ? "slack_approval_pending" : "slack_install_declined"; + if (scenario === "approval_denied") endpoint.setup!.slackRegistration!.errorCode = "slack_approval_denied"; if (scenario === "recovery") endpoint.setup!.slackRegistration = { ...endpoint.setup!.slackRegistration!, status: "credentials_saved", errorCode: "slack_configuration_incomplete" }; - if (scenario === "uncertain") endpoint.setup!.slackRegistration = { status: "uncertain", errorCode: "slack_creation_uncertain", managementUrl: "https://api.slack.com/apps" }; + if (scenario === "uncertain") endpoint.setup!.slackRegistration = { status: "uncertain", ...(managed ? { managerGrantId: grantId } : {}), errorCode: "slack_creation_uncertain", managementUrl: "https://api.slack.com/apps" }; if (["verify", "avatar_failed", "welcome_failed", "success"].includes(scenario)) installed(); if (["avatar_failed", "welcome_failed", "success"].includes(scenario)) { endpoint.setup!.webhookVerifiedAt = new Date().toISOString(); @@ -52,12 +55,26 @@ export function SlackSetupFixture({ scenario = "create" }: { scenario?: SlackSet window.fetch = async (input, init) => { const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url, window.location.origin); const path = url.pathname; + if (path === "/api/companies/company-storybook/chat-slack/setup-options") { + if (optionsDelayMs) await new Promise(resolve => setTimeout(resolve, optionsDelayMs)); + if (optionsError) return Response.json({ error: "Slack setup unavailable" }, { status: 503 }); + return Response.json({ managedAvailable: managed && managedAvailable, defaultMethod: managed ? "managed" : "automatic", workspaces: managed && authorized ? Array.from({ length: Math.max(1, workspaceCount) }, (_, i) => ({ grantId: i ? "22222222-2222-4222-8222-222222222222" : grantId, workspaceId: `TSTORY${i}`, workspaceName: i ? "Research workspace" : "Paperclip", userId: "UPERSON" })) : [] }); + } + if (path === "/api/chat-endpoints/slack-story/slack/managed/authorize") { + authorized = true; + const returnUrl = new URL(window.location.href); + returnUrl.hash = ""; + returnUrl.searchParams.set("slackFixtureAuthorized", "1"); + return Response.json({ authorizationUrl: returnUrl.toString(), expiresAt: new Date(Date.now() + 600_000).toISOString() }); + } + if (path === "/api/chat-endpoints/slack-story/slack/managed/provision") { installed(); return Response.json({}); } if (path === "/api/companies/company-storybook/agents") return Response.json(agents); if (path === `/api/agents/${agent.id}`) return Response.json(agent); if (path === "/api/companies/company-storybook/chat-endpoints") { const input = JSON.parse(String(init?.body ?? "{}")); agent = agents.find(value => value.id === input.assignedAgentId) ?? agent; endpoint.assignedAgentId = agent.id; endpoint.assignedAgentName = agent.name; + endpoint.setup!.slackSetupMethod = input.slackSetupMethod ?? (managed ? "managed" : "automatic"); endpoint.setup!.slackApp = input.slackApp ?? defaultSlackAppConfiguration(agent.name); return Response.json(endpoint); } @@ -94,7 +111,7 @@ export function SlackSetupFixture({ scenario = "create" }: { scenario?: SlackSet }; setReady(true); return () => { window.fetch = original; }; - }, [scenario]); + }, [scenario, managed, managedAvailable, workspaceCount, optionsDelayMs, optionsError]); return
; } -const meta = { title: "Agents/Persistent files", component: AgentFilesStory, parameters: { layout: "fullscreen" } } satisfies Meta; +const meta = { id: "agents-persistent-files", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Persistent files", component: AgentFilesStory, parameters: { layout: "fullscreen" } } satisfies Meta; export default meta; type Story = StoryObj; export const InstructionsAndFiles: Story = {}; diff --git a/ui/storybook/stories/agent-management.stories.tsx b/ui/storybook/stories/agents/agent-management.stories.tsx similarity index 99% rename from ui/storybook/stories/agent-management.stories.tsx rename to ui/storybook/stories/agents/agent-management.stories.tsx index 7f1bee25fcc..2b877ad5cee 100644 --- a/ui/storybook/stories/agent-management.stories.tsx +++ b/ui/storybook/stories/agents/agent-management.stories.tsx @@ -38,7 +38,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; -import { storybookAgents, storybookIssues } from "../fixtures/paperclipData"; +import { storybookAgents, storybookIssues } from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const now = new Date("2026-04-20T12:00:00.000Z"); @@ -819,7 +819,8 @@ function AgentManagementStories() { } const meta = { - title: "Product/Agent Management", + id: "product-agent-management", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Product/Agent Management", component: AgentManagementStories, parameters: { docs: { diff --git a/ui/storybook/stories/agent-persona-pages.stories.tsx b/ui/storybook/stories/agents/agent-persona-pages.stories.tsx similarity index 96% rename from ui/storybook/stories/agent-persona-pages.stories.tsx rename to ui/storybook/stories/agents/agent-persona-pages.stories.tsx index 0ae8a727590..f220ae53f89 100644 --- a/ui/storybook/stories/agent-persona-pages.stories.tsx +++ b/ui/storybook/stories/agents/agent-persona-pages.stories.tsx @@ -14,7 +14,7 @@ import { NewAgent } from "@/pages/NewAgent"; import { AgentBasicsDialog } from "@/components/new-agent/AgentBasicsDialog"; import { queryKeys } from "@/lib/queryKeys"; import { resolveAgentAppearance } from "@paperclipai/shared"; -import { storybookAgents, storybookIssues, storybookActivityEvents, storybookLiveRuns, storybookDashboardSummary } from "../fixtures/paperclipData"; +import { storybookAgents, storybookIssues, storybookActivityEvents, storybookLiveRuns, storybookDashboardSummary } from "../../fixtures/paperclipData"; const companyId = "company-storybook"; const agents = storybookAgents.map(agent => { @@ -109,7 +109,8 @@ function PersonaPage({ path, meet = false }: { path: string; meet?: boolean }) { {meet && setDialogOpen(false)} onContinue={basics => { setDialogOpen(false); navigate(`/PAP/agents/new?name=${encodeURIComponent(basics.name)}&adapterType=${basics.adapterType}`); }} />} ; } -const meta = { title: "Agents/Personas/Full pages", parameters: { layout: "fullscreen", a11y: { test: "off" } } } satisfies Meta; +const meta = { id: "agents-personas-full-pages", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Personas/Full pages", parameters: { layout: "fullscreen", a11y: { test: "off" } } } satisfies Meta; export default meta; type Story = StoryObj; export const AllAgents: Story = { render: () => }; diff --git a/ui/storybook/stories/agent-personas.stories.tsx b/ui/storybook/stories/agents/agent-personas.stories.tsx similarity index 95% rename from ui/storybook/stories/agent-personas.stories.tsx rename to ui/storybook/stories/agents/agent-personas.stories.tsx index d4a70696294..57aeec4a853 100644 --- a/ui/storybook/stories/agent-personas.stories.tsx +++ b/ui/storybook/stories/agents/agent-personas.stories.tsx @@ -3,15 +3,16 @@ import { expect, waitFor } from "storybook/test"; import { useEffect, useRef, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { AGENT_PALETTE_IDS, AGENT_AVATAR_SIZES, CHARACTER_STATES, appearanceForPalette, type CharacterState } from "@paperclipai/shared"; -import { AgentAvatar, avatarSizeClasses } from "../../src/components/AgentAvatar"; -import { AgentCharacter } from "../../src/components/AgentCharacter"; -import { AgentIdentity } from "../../src/components/AgentIdentity"; -import { Button } from "../../src/components/ui/button"; +import { AgentAvatar, avatarSizeClasses } from "../../../src/components/AgentAvatar"; +import { AgentCharacter } from "../../../src/components/AgentCharacter"; +import { AgentIdentity } from "../../../src/components/AgentIdentity"; +import { Button } from "../../../src/components/ui/button"; const appearance = appearanceForPalette("bubblegum-sky"); const agent = { id: "storybook-agent", name: "Chief of Staff", appearance }; const meta = { - title: "Agents/Personas", + id: "agents-personas", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Personas", component: AgentCharacter, args: { appearance, size: 256, state: "listening", label: "Chief of Staff" }, parameters: { docs: { description: { component: "Persistent cap-v1 identities. The hero alone loads ClipLab. Development Storybook renders capsule PNGs locally on demand; published Storybook packages the same PNGs during its build, including every preset and both densities. Neither needs a running API." } } }, diff --git a/ui/storybook/stories/agent-settings-redesign.stories.tsx b/ui/storybook/stories/agents/agent-settings-redesign.stories.tsx similarity index 91% rename from ui/storybook/stories/agent-settings-redesign.stories.tsx rename to ui/storybook/stories/agents/agent-settings-redesign.stories.tsx index 8a29b6c0a83..5a0d1bcd92f 100644 --- a/ui/storybook/stories/agent-settings-redesign.stories.tsx +++ b/ui/storybook/stories/agents/agent-settings-redesign.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { AgentSettingsPreview } from "../prototypes/agent-settings/AgentSettingsPreview"; +import { AgentSettingsPreview } from "../../prototypes/agent-settings/AgentSettingsPreview"; const meta = { - title: "Agents/Configuration refresh", + id: "agents-configuration-refresh", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Agents/Configuration refresh", component: AgentSettingsPreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/built-in-agents.stories.tsx b/ui/storybook/stories/agents/built-in-agents.stories.tsx similarity index 98% rename from ui/storybook/stories/built-in-agents.stories.tsx rename to ui/storybook/stories/agents/built-in-agents.stories.tsx index b0bf66ddf84..9364b3fd3ab 100644 --- a/ui/storybook/stories/built-in-agents.stories.tsx +++ b/ui/storybook/stories/agents/built-in-agents.stories.tsx @@ -111,7 +111,8 @@ function SectionLabel({ children }: { children: React.ReactNode }) { } const meta: Meta = { - title: "Product/Built-in Agents", + id: "product-built-in-agents", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Product/Built-in Agents", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/external-agent-invite/components.stories.tsx b/ui/storybook/stories/agents/external-agent-invite/components.stories.tsx similarity index 88% rename from ui/storybook/stories/external-agent-invite/components.stories.tsx rename to ui/storybook/stories/agents/external-agent-invite/components.stories.tsx index be2808170f3..6e08aaa085e 100644 --- a/ui/storybook/stories/external-agent-invite/components.stories.tsx +++ b/ui/storybook/stories/agents/external-agent-invite/components.stories.tsx @@ -3,7 +3,8 @@ import { fn } from "storybook/test"; import { DotConnectionChecks, ExternalAgentPresetPicker } from "@/components/new-agent/ExternalAgentInviteContent"; const meta = { - title: "Onboarding/External agent invitation/Components", + id: "onboarding-external-agent-invitation-components", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/External agent invitation/Components", component: DotConnectionChecks, parameters: { layout: "centered", docs: { description: { component: "Controlled presentation states. Connection checks must be driven by server evidence. Subscription verification alone must never mark the connection ready." } } }, decorators: [Story =>
], diff --git a/ui/storybook/stories/external-agent-invite/journeys.stories.tsx b/ui/storybook/stories/agents/external-agent-invite/journeys.stories.tsx similarity index 96% rename from ui/storybook/stories/external-agent-invite/journeys.stories.tsx rename to ui/storybook/stories/agents/external-agent-invite/journeys.stories.tsx index 0c002c46722..31d87021728 100644 --- a/ui/storybook/stories/external-agent-invite/journeys.stories.tsx +++ b/ui/storybook/stories/agents/external-agent-invite/journeys.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; -import { ExternalAgentInvitePreview } from "./ExternalAgentInvitePreview"; +import { ExternalAgentInvitePreview } from "../../../support/external-agent-invite/ExternalAgentInvitePreview"; const meta = { - title: "Onboarding/External agent invitation/Journeys", + id: "onboarding-external-agent-invitation-journeys", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/External agent invitation/Journeys", component: ExternalAgentInvitePreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/new-agent-onboarding.stories.tsx b/ui/storybook/stories/agents/new-agent-onboarding.stories.tsx similarity index 97% rename from ui/storybook/stories/new-agent-onboarding.stories.tsx rename to ui/storybook/stories/agents/new-agent-onboarding.stories.tsx index c867442654e..0c7c0a963d4 100644 --- a/ui/storybook/stories/new-agent-onboarding.stories.tsx +++ b/ui/storybook/stories/agents/new-agent-onboarding.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; -import { NewAgentWizard } from "../prototypes/NewAgentWizard"; +import { NewAgentWizard } from "../../prototypes/NewAgentWizard"; const meta = { - title: "Onboarding/New agent", + id: "onboarding-new-agent", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Onboarding/New agent", component: NewAgentWizard, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/primary-agent/00-review-guide.stories.tsx b/ui/storybook/stories/agents/primary-agent/00-review-guide.stories.tsx similarity index 95% rename from ui/storybook/stories/primary-agent/00-review-guide.stories.tsx rename to ui/storybook/stories/agents/primary-agent/00-review-guide.stories.tsx index 229bef555d5..851d249fe76 100644 --- a/ui/storybook/stories/primary-agent/00-review-guide.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/00-review-guide.stories.tsx @@ -50,5 +50,6 @@ function ReviewGuide() {
; } -export default { title: "Primary Agent/00 Review guide", parameters: { layout: "fullscreen" }, render: () => } satisfies Meta; +export default { id: "primary-agent-00-review-guide", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/00 Review guide", parameters: { layout: "fullscreen" }, render: () => } satisfies Meta; export const StartHere: StoryObj = {}; diff --git a/ui/storybook/stories/primary-agent/01-components.stories.tsx b/ui/storybook/stories/agents/primary-agent/01-components.stories.tsx similarity index 73% rename from ui/storybook/stories/primary-agent/01-components.stories.tsx rename to ui/storybook/stories/agents/primary-agent/01-components.stories.tsx index 339a71269fe..20aad95c2f9 100644 --- a/ui/storybook/stories/primary-agent/01-components.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/01-components.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { AgentIdentity } from "@/components/AgentIdentity"; import { PrimaryAgentIndicator } from "@/components/primary-agent/PrimaryAgentPresentation"; -import { primaryDecorator, primaryParameters, usePrimaryStory, type PrimaryScenario } from "./PrimaryAgentStory"; +import { primaryDecorator, primaryParameters, usePrimaryStory, type PrimaryScenario } from "../../../support/primary-agent/PrimaryAgentStory"; function Components(_args: PrimaryScenario) { const { agents } = usePrimaryStory(); @@ -11,7 +11,8 @@ function Components(_args: PrimaryScenario) { )}
; } -export default { title: "Primary Agent/01 Components", component: Components, decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, render: args => } satisfies Meta; +export default { id: "primary-agent-01-components", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/01 Components", component: Components, decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, render: args => } satisfies Meta; type Story = StoryObj; export const CrownIndicator: Story = {}; export const NoPrimary: Story = { args: { primaryId: null } }; diff --git a/ui/storybook/stories/primary-agent/02-sidebar.stories.tsx b/ui/storybook/stories/agents/primary-agent/02-sidebar.stories.tsx similarity index 76% rename from ui/storybook/stories/primary-agent/02-sidebar.stories.tsx rename to ui/storybook/stories/agents/primary-agent/02-sidebar.stories.tsx index e392af89f73..ca5b0d1056c 100644 --- a/ui/storybook/stories/primary-agent/02-sidebar.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/02-sidebar.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; -import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; -export default { title: "Primary Agent/02 Sidebar", decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, argTypes: { primaryName: { control: "text" } }, render: () => } satisfies Meta; +export default { id: "primary-agent-02-sidebar", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/02 Sidebar", decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, argTypes: { primaryName: { control: "text" } }, render: () => } satisfies Meta; type Story = StoryObj; export const IdlePrimary: Story = {}; export const ActiveColleague: Story = { parameters: { primaryAgent: { active: true } } }; diff --git a/ui/storybook/stories/primary-agent/03-agents.stories.tsx b/ui/storybook/stories/agents/primary-agent/03-agents.stories.tsx similarity index 78% rename from ui/storybook/stories/primary-agent/03-agents.stories.tsx rename to ui/storybook/stories/agents/primary-agent/03-agents.stories.tsx index ae4d8115a3f..17ae07fb2bc 100644 --- a/ui/storybook/stories/primary-agent/03-agents.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/03-agents.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; -export default { title: "Primary Agent/03 Agents", decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, argTypes: { primaryName: { control: "text" } }, render: () => } satisfies Meta; +export default { id: "primary-agent-03-agents", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/03 Agents", decorators: [primaryDecorator], parameters: primaryParameters, args: { primaryName: "Maia" }, argTypes: { primaryName: { control: "text" } }, render: () => } satisfies Meta; type Story = StoryObj; export const Roster: Story = {}; export const Profile: Story = { parameters: { initialEntries: ["/PAP/agents/maia"] } }; diff --git a/ui/storybook/stories/primary-agent/04-new-task.stories.tsx b/ui/storybook/stories/agents/primary-agent/04-new-task.stories.tsx similarity index 74% rename from ui/storybook/stories/primary-agent/04-new-task.stories.tsx rename to ui/storybook/stories/agents/primary-agent/04-new-task.stories.tsx index 62a316e629e..e3d78f4d0a4 100644 --- a/ui/storybook/stories/primary-agent/04-new-task.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/04-new-task.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; -export default { title: "Primary Agent/04 New task", decorators: [primaryDecorator], parameters: primaryParameters, render: () => } satisfies Meta; +export default { id: "primary-agent-04-new-task", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/04 New task", decorators: [primaryDecorator], parameters: primaryParameters, render: () => } satisfies Meta; type Story = StoryObj; export const PrimaryFallback: Story = {}; export const RecentSpecialistWins: Story = { parameters: { primaryAgent: { recentAssignee: true } }, render: () => }; diff --git a/ui/storybook/stories/primary-agent/05-chat.stories.tsx b/ui/storybook/stories/agents/primary-agent/05-chat.stories.tsx similarity index 68% rename from ui/storybook/stories/primary-agent/05-chat.stories.tsx rename to ui/storybook/stories/agents/primary-agent/05-chat.stories.tsx index b5a2f3fa2aa..e487734f4dd 100644 --- a/ui/storybook/stories/primary-agent/05-chat.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/05-chat.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; -export default { title: "Primary Agent/05 Chat", decorators: [primaryDecorator], parameters: { ...primaryParameters, initialEntries: ["/PAP/chats"] }, render: () => } satisfies Meta; +export default { id: "primary-agent-05-chat", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/05 Chat", decorators: [primaryDecorator], parameters: { ...primaryParameters, initialEntries: ["/PAP/chats"] }, render: () => } satisfies Meta; type Story = StoryObj; export const PrimaryFallback: Story = {}; export const RecentConversationWins: Story = { parameters: { primaryAgent: { recentChat: true } } }; diff --git a/ui/storybook/stories/primary-agent/06-journeys.stories.tsx b/ui/storybook/stories/agents/primary-agent/06-journeys.stories.tsx similarity index 95% rename from ui/storybook/stories/primary-agent/06-journeys.stories.tsx rename to ui/storybook/stories/agents/primary-agent/06-journeys.stories.tsx index 0172759b91f..c4496e5118e 100644 --- a/ui/storybook/stories/primary-agent/06-journeys.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/06-journeys.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; -import { PrimaryPage, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; async function openActions(canvasElement: HTMLElement, name: string, profile = false) { const page = within(canvasElement.ownerDocument.body); @@ -11,7 +11,8 @@ async function openActions(canvasElement: HTMLElement, name: string, profile = f return page; } const maiaProfile = { initialEntries: ["/PAP/agents/maia"] }; -export default { title: "Primary Agent/06 Journeys", decorators: [primaryDecorator], parameters: { ...primaryParameters, initialEntries: ["/PAP/agents/alex"] }, render: () => } satisfies Meta; +export default { id: "primary-agent-06-journeys", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/06 Journeys", decorators: [primaryDecorator], parameters: { ...primaryParameters, initialEntries: ["/PAP/agents/alex"] }, render: () => } satisfies Meta; type Story = StoryObj; async function confirmPrimaryChange(canvasElement: HTMLElement) { const page = within(canvasElement.ownerDocument.body); diff --git a/ui/storybook/stories/primary-agent/07-confirmation.stories.tsx b/ui/storybook/stories/agents/primary-agent/07-confirmation.stories.tsx similarity index 82% rename from ui/storybook/stories/primary-agent/07-confirmation.stories.tsx rename to ui/storybook/stories/agents/primary-agent/07-confirmation.stories.tsx index 73329989d76..be3e59897d7 100644 --- a/ui/storybook/stories/primary-agent/07-confirmation.stories.tsx +++ b/ui/storybook/stories/agents/primary-agent/07-confirmation.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "./PrimaryAgentStory"; +import { PrimaryPage, mobile, primaryDecorator, primaryParameters } from "../../../support/primary-agent/PrimaryAgentStory"; export default { - title: "Primary Agent/07 Confirmation", + id: "primary-agent-07-confirmation", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Primary Agent/07 Confirmation", decorators: [primaryDecorator], parameters: { ...primaryParameters, initialEntries: ["/PAP/agents/alex"] }, render: () => , diff --git a/ui/storybook/stories/summary-slot-card.stories.tsx b/ui/storybook/stories/agents/summary-slot-card.stories.tsx similarity index 98% rename from ui/storybook/stories/summary-slot-card.stories.tsx rename to ui/storybook/stories/agents/summary-slot-card.stories.tsx index 9a91ef710f2..2742160f79b 100644 --- a/ui/storybook/stories/summary-slot-card.stories.tsx +++ b/ui/storybook/stories/agents/summary-slot-card.stories.tsx @@ -325,7 +325,8 @@ const DESKTOP = 640; const MOBILE = 375; const meta: Meta = { - title: "Summaries/SummarySlotCard", + id: "summaries-summaryslotcard", + title: "Agents and reusable capabilities/Hiring, configuration, and organization/Summaries/SummarySlotCard", component: CardHarness, parameters: { layout: "padded" }, }; diff --git a/ui/storybook/stories/action-card.stories.tsx b/ui/storybook/stories/app-permissions/action-card.stories.tsx similarity index 95% rename from ui/storybook/stories/action-card.stories.tsx rename to ui/storybook/stories/app-permissions/action-card.stories.tsx index 869b070ac91..5222edf27ef 100644 --- a/ui/storybook/stories/action-card.stories.tsx +++ b/ui/storybook/stories/app-permissions/action-card.stories.tsx @@ -21,7 +21,8 @@ const baseProps = { }; const meta: Meta = { - title: "Tools & Access/ActionCard", + id: "tools-access-actioncard", + title: "Apps, channels, and extensions/App access and action permissions/Tools & Access/ActionCard", component: ActionCard, }; export default meta; diff --git a/ui/storybook/stories/action-permissions-test.stories.tsx b/ui/storybook/stories/app-permissions/action-permissions-test.stories.tsx similarity index 98% rename from ui/storybook/stories/action-permissions-test.stories.tsx rename to ui/storybook/stories/app-permissions/action-permissions-test.stories.tsx index 38f2601ca3d..235a8fde921 100644 --- a/ui/storybook/stories/action-permissions-test.stories.tsx +++ b/ui/storybook/stories/app-permissions/action-permissions-test.stories.tsx @@ -177,7 +177,8 @@ function PermissionsTestStory({ result, tool = TOOL, appName = "Asana", query }: } const meta = { - title: "Apps/Action permissions test", + id: "apps-action-permissions-test", + title: "Apps, channels, and extensions/App access and action permissions/Apps/Action permissions test", parameters: { layout: "fullscreen" }, } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/app-permissions/apps-managed-accounts.stories.tsx b/ui/storybook/stories/app-permissions/apps-managed-accounts.stories.tsx new file mode 100644 index 00000000000..7fffdf2e6ef --- /dev/null +++ b/ui/storybook/stories/app-permissions/apps-managed-accounts.stories.tsx @@ -0,0 +1,7 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { ManagedAggregatorAccounts } from "../../prototypes/ManagedAggregatorAccounts"; +const meta = { id: "apps-managed-accounts", + title: "Apps, channels, and extensions/App access and action permissions/Apps/Managed accounts", component: ManagedAggregatorAccounts, parameters: { layout: "fullscreen", docs: { description: { component: "Production Apps page using production API routes and a disposable PostgreSQL database. Upstream Arcade, Composio and Executor responses are fixtures. Start tests/aggregator-accounts/test-drive.ts, then use the upstream controls and each gateway’s Refresh action." } } } } satisfies Meta; +export default meta; +type Story = StoryObj; +export const FullStackTestDrive: Story = {}; diff --git a/ui/storybook/stories/permitted-vs-installed.stories.tsx b/ui/storybook/stories/app-permissions/permitted-vs-installed.stories.tsx similarity index 98% rename from ui/storybook/stories/permitted-vs-installed.stories.tsx rename to ui/storybook/stories/app-permissions/permitted-vs-installed.stories.tsx index 6d5874bc630..07bb33c8104 100644 --- a/ui/storybook/stories/permitted-vs-installed.stories.tsx +++ b/ui/storybook/stories/app-permissions/permitted-vs-installed.stories.tsx @@ -122,7 +122,8 @@ function connection(id: string, name: string, installs: InstallState): ToolConne } const meta: Meta = { - title: "Tools/Permitted vs Installed", + id: "tools-permitted-vs-installed", + title: "Apps, channels, and extensions/App access and action permissions/Tools/Permitted vs Installed", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/apps-managed-accounts.stories.tsx b/ui/storybook/stories/apps-managed-accounts.stories.tsx deleted file mode 100644 index fdff3c42e03..00000000000 --- a/ui/storybook/stories/apps-managed-accounts.stories.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ManagedAggregatorAccounts } from "../prototypes/ManagedAggregatorAccounts"; -const meta = { title: "Apps/Managed accounts", component: ManagedAggregatorAccounts, parameters: { layout: "fullscreen", docs: { description: { component: "Production Apps page using production API routes and a disposable PostgreSQL database. Upstream Arcade, Composio and Executor responses are fixtures. Start tests/aggregator-accounts/test-drive.ts, then use the upstream controls and each gateway’s Refresh action." } } } } satisfies Meta; -export default meta; -type Story = StoryObj; -export const FullStackTestDrive: Story = {}; diff --git a/ui/storybook/stories/budget-finance.stories.tsx b/ui/storybook/stories/budgets-costs/budget-finance.stories.tsx similarity index 99% rename from ui/storybook/stories/budget-finance.stories.tsx rename to ui/storybook/stories/budgets-costs/budget-finance.stories.tsx index 21cbe1df0c2..df59337d580 100644 --- a/ui/storybook/stories/budget-finance.stories.tsx +++ b/ui/storybook/stories/budgets-costs/budget-finance.stories.tsx @@ -765,7 +765,8 @@ function BudgetFinanceMatrix() { } const meta = { - title: "Product/Budget & Finance", + id: "product-budget-finance", + title: "Oversight and operation/Costs and budgets/Product/Budget & Finance", component: BudgetFinanceMatrix, parameters: { docs: { diff --git a/ui/storybook/stories/costs/subscriptions.stories.tsx b/ui/storybook/stories/budgets-costs/costs/subscriptions.stories.tsx similarity index 98% rename from ui/storybook/stories/costs/subscriptions.stories.tsx rename to ui/storybook/stories/budgets-costs/costs/subscriptions.stories.tsx index 3173cb6259a..6d55bb84345 100644 --- a/ui/storybook/stories/costs/subscriptions.stories.tsx +++ b/ui/storybook/stories/budgets-costs/costs/subscriptions.stories.tsx @@ -100,7 +100,8 @@ function SubscriptionCostsStory({ unknownPrice = false, stale = false, costGaps return ready ? <> : null; } -const meta = { title: "Costs/Subscriptions", component: SubscriptionCostsStory, parameters: { +const meta = { id: "costs-subscriptions", + title: "Oversight and operation/Costs and budgets/Costs/Subscriptions", component: SubscriptionCostsStory, parameters: { layout: "padded", docs: { description: { component: "Production Costs page with API spend, monthly subscription estimates, user and project costs. View details to edit monthly or annual prices. Provider calls are fixtures." } }, } } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/agentmail-onboarding.stories.tsx b/ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx similarity index 98% rename from ui/storybook/stories/agentmail-onboarding.stories.tsx rename to ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx index 30607a45286..0542a1c4bfc 100644 --- a/ui/storybook/stories/agentmail-onboarding.stories.tsx +++ b/ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx @@ -6,7 +6,7 @@ import { ChatSetupSidebar } from "@/components/chat/ChatSetupNavigation"; import { ChatSetupSidebarProvider } from "@/context/ChatSetupSidebarContext"; import { useCompany } from "@/context/CompanyContext"; import { useLocation, useNavigate } from "@/lib/router"; -import { storybookAgents } from "../fixtures/paperclipData"; +import { storybookAgents } from "../../fixtures/paperclipData"; const COMPANY = "company-storybook"; const CONNECTION = "agentmail-storybook-account"; @@ -40,7 +40,8 @@ function AgentMailJourney({ savedConnection = true }: { savedConnection?: boolea type Scenario = { noSavedKeys?: boolean; scopedKey?: boolean; missingBoundary?: boolean; denySetup?: boolean; initialTaken?: boolean; noCustomDomain?: boolean; checkFailed?: boolean }; const meta = { - title: "Connections/AgentMail setup", + id: "connections-agentmail-setup", + title: "Apps, channels, and extensions/External chat and email/Connections/AgentMail setup", component: AgentMailJourney, parameters: { layout: "fullscreen" }, args: { savedConnection: true }, diff --git a/ui/storybook/stories/agentmail-settings.stories.tsx b/ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx similarity index 97% rename from ui/storybook/stories/agentmail-settings.stories.tsx rename to ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx index 0d22f49ddcb..cb992253cab 100644 --- a/ui/storybook/stories/agentmail-settings.stories.tsx +++ b/ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx @@ -6,7 +6,8 @@ import type { EmailEndpointSummary } from "@paperclipai/shared"; const COMPANY = "company-storybook"; const ENDPOINT = "agentmail-settings-preview"; const meta = { - title: "Connections/AgentMail settings", + id: "connections-agentmail-settings", + title: "Apps, channels, and extensions/External chat and email/Connections/AgentMail settings", component: EmailEndpointSettings, parameters: { layout: "padded" }, args: { endpointId: ENDPOINT, companyId: COMPANY, assignedAgentName: "Ralph" }, diff --git a/ui/storybook/stories/agentmail.stories.tsx b/ui/storybook/stories/chat-channels/agentmail.stories.tsx similarity index 99% rename from ui/storybook/stories/agentmail.stories.tsx rename to ui/storybook/stories/chat-channels/agentmail.stories.tsx index 9e56b4fef70..0089529b4db 100644 --- a/ui/storybook/stories/agentmail.stories.tsx +++ b/ui/storybook/stories/chat-channels/agentmail.stories.tsx @@ -545,7 +545,8 @@ function EmailTaskExperience({ ); } const meta = { - title: "Connections/AgentMail tasks", + id: "connections-agentmail-tasks", + title: "Apps, channels, and extensions/External chat and email/Connections/AgentMail tasks", component: EmailTaskExperience, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/chat-connection-actions.stories.tsx b/ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx similarity index 93% rename from ui/storybook/stories/chat-connection-actions.stories.tsx rename to ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx index b96e48bafd7..dc1793dd292 100644 --- a/ui/storybook/stories/chat-connection-actions.stories.tsx +++ b/ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx @@ -10,7 +10,8 @@ const endpoint: ChatEndpoint = { }; const meta = { - title: "Connections/Chat connection actions", + id: "connections-chat-connection-actions", + title: "Apps, channels, and extensions/External chat and email/Connections/Chat connection actions", component: ConnectorCard, parameters: { layout: "padded" }, args: { diff --git a/ui/storybook/stories/chat-management/chat-management.stories.tsx b/ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx similarity index 98% rename from ui/storybook/stories/chat-management/chat-management.stories.tsx rename to ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx index 6160849cbdc..b6246aaba58 100644 --- a/ui/storybook/stories/chat-management/chat-management.stories.tsx +++ b/ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx @@ -9,7 +9,7 @@ import { PluginLauncherProvider } from "@/plugins/launchers"; import { ChatEndpointDetail } from "@/pages/apps/chat/ChatEndpointDetail"; import { useCompany } from "@/context/CompanyContext"; import type { ChatEndpoint, ChatEndpointResource, ChatIdentityLink } from "@/api/chatEndpoints"; -import { storybookAgents } from "../../fixtures/paperclipData"; +import { storybookAgents } from "../../../fixtures/paperclipData"; type Scenario = "populated" | "empty" | "error" | "channel-added" | "unlinked" | "invitation-expired"; function ChatManagementPage({ tab = "settings", scenario = "populated" }: { tab?: string; scenario?: Scenario }) { @@ -105,7 +105,8 @@ function ChatManagementPage({ tab = "settings", scenario = "populated" }: { tab? } const meta = { - title: "Connections/Chat management", + id: "connections-chat-management", + title: "Apps, channels, and extensions/External chat and email/Connections/Chat management", component: ChatManagementPage, parameters: { layout: "fullscreen", a11y: { test: "off" } }, args: { tab: "settings", scenario: "populated" }, diff --git a/ui/storybook/stories/github-bot-management/management.stories.tsx b/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx similarity index 97% rename from ui/storybook/stories/github-bot-management/management.stories.tsx rename to ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx index 0d32d841935..38b2ed87ee0 100644 --- a/ui/storybook/stories/github-bot-management/management.stories.tsx +++ b/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx @@ -32,11 +32,12 @@ import { configuration, reviews, conversations, -} from "./fixtures"; -import { FixtureApi, type FixtureState } from "./fixture-api"; +} from "../../../support/github-bot-management/fixtures"; +import { FixtureApi, type FixtureState } from "../../../support/github-bot-management/fixture-api"; const meta = { - title: "Connectors/GitHub bot management", + id: "connectors-github-bot-management", + title: "Apps, channels, and extensions/External chat and email/Connectors/GitHub bot management", parameters: { layout: "fullscreen", initialEntries: [`/PAP/apps/chat/${endpoint.id}/settings`], diff --git a/ui/storybook/stories/github-chat.stories.tsx b/ui/storybook/stories/chat-channels/github-chat.stories.tsx similarity index 98% rename from ui/storybook/stories/github-chat.stories.tsx rename to ui/storybook/stories/chat-channels/github-chat.stories.tsx index 5e6d09b6e25..b32fb9383d8 100644 --- a/ui/storybook/stories/github-chat.stories.tsx +++ b/ui/storybook/stories/chat-channels/github-chat.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { GitHubChatPreview } from "../prototypes/github-chat/GitHubChatPreview"; +import { GitHubChatPreview } from "../../prototypes/github-chat/GitHubChatPreview"; const meta = { - title: "Historical/GitHub chat prototypes", + id: "historical-github-chat-prototypes", + title: "Apps, channels, and extensions/External chat and email/Historical/GitHub chat prototypes", component: GitHubChatPreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/github-connections.stories.tsx b/ui/storybook/stories/chat-channels/github-connections.stories.tsx similarity index 96% rename from ui/storybook/stories/github-connections.stories.tsx rename to ui/storybook/stories/chat-channels/github-connections.stories.tsx index ed15b4ef80e..a176546e78d 100644 --- a/ui/storybook/stories/github-connections.stories.tsx +++ b/ui/storybook/stories/chat-channels/github-connections.stories.tsx @@ -10,7 +10,7 @@ import { queryKeys } from "@/lib/queryKeys"; import { Browse } from "@/pages/apps/Browse"; import { AppsConnect } from "@/pages/apps/AppsConnect"; import { ChatEndpointSetup } from "@/pages/apps/chat/ChatEndpointSetup"; -import { storybookAgents } from "../fixtures/paperclipData"; +import { storybookAgents } from "../../fixtures/paperclipData"; const companyId = "company-storybook"; // Hosted Storybooks live below a branch path; reuse their bundled GitHub marks. @@ -78,7 +78,8 @@ function GitHubConnections({ saved = false, chatEnabled = true }) { } const meta = { - title: "Connections/GitHub and Code Review Bot", + id: "connections-github-and-code-review-bot", + title: "Apps, channels, and extensions/External chat and email/Connections/GitHub and Code Review Bot", component: GitHubConnections, play: async ({ canvasElement }) => { await userEvent.type(await within(canvasElement).findByRole("searchbox", { name: "Search connectors" }), "GitHub"); diff --git a/ui/storybook/stories/imessage-photon.stories.tsx b/ui/storybook/stories/chat-channels/imessage-photon.stories.tsx similarity index 98% rename from ui/storybook/stories/imessage-photon.stories.tsx rename to ui/storybook/stories/chat-channels/imessage-photon.stories.tsx index 13a227a48d8..00c7b2c4621 100644 --- a/ui/storybook/stories/imessage-photon.stories.tsx +++ b/ui/storybook/stories/chat-channels/imessage-photon.stories.tsx @@ -11,7 +11,7 @@ import { TaskChatBubble } from "@/components/task-chat/TaskChatBubble"; import { Button } from "@/components/ui/button"; import type { ChatEndpoint, ChatIdentityLink } from "@/api/chatEndpoints"; import type { TaskChatMessageItem } from "@/components/task-chat/task-chat-model"; -import { storybookAgents } from "../fixtures/paperclipData"; +import { storybookAgents } from "../../fixtures/paperclipData"; type Screen = "catalog" | "agent" | "credentials" | "access" | "task" | "reconnect"; type Scenario = { screen?: Screen; dedicated?: boolean; outage?: boolean; noLine?: boolean; loading?: boolean; connecting?: boolean }; @@ -96,7 +96,8 @@ function Host(props: { screen?: Screen }) { } const meta = { - title: "Connections/iMessage Photon", component: Host, + id: "connections-imessage-photon", + title: "Apps, channels, and extensions/External chat and email/Connections/iMessage Photon", component: Host, parameters: { layout: "fullscreen" }, beforeEach: ({ parameters }) => { const scenario = (parameters.photonScenario ?? {}) as Scenario; diff --git a/ui/storybook/stories/slack-automatic-setup.stories.tsx b/ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx similarity index 95% rename from ui/storybook/stories/slack-automatic-setup.stories.tsx rename to ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx index 4ed65ff6032..813937e17b7 100644 --- a/ui/storybook/stories/slack-automatic-setup.stories.tsx +++ b/ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { SlackSetupFixture } from "../fixtures/SlackSetupFixture"; +import { SlackSetupFixture } from "../../fixtures/SlackSetupFixture"; const start = "/PAP/apps/chat/connect?provider=slack&purpose=chat"; const meta = { - title: "Connections/Slack/Automatic setup", + id: "connections-slack-automatic-setup", + title: "Apps, channels, and extensions/External chat and email/Connections/Slack/Automatic setup", component: SlackSetupFixture, parameters: { layout: "fullscreen", initialEntries: [`${start}&resume=slack-story`], docs: { description: { component: "Production Slack setup pages in journey order. Automatic stories use the same four-step wizard; manual setup retains account linking. Slack creation, consent, verification, and identity discovery are simulated; use synthetic tokens only." } } }, diff --git a/ui/storybook/stories/slack-avatar-settings.stories.tsx b/ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx similarity index 67% rename from ui/storybook/stories/slack-avatar-settings.stories.tsx rename to ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx index 96ce07e8110..011d9367cf9 100644 --- a/ui/storybook/stories/slack-avatar-settings.stories.tsx +++ b/ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { SlackAvatarSettingsPreview } from "../prototypes/slack-avatar/SlackAvatarSettingsPreview"; +import { SlackAvatarSettingsPreview } from "../../prototypes/slack-avatar/SlackAvatarSettingsPreview"; const meta = { - title: "Connections/Slack/Avatar in Settings", + id: "connections-slack-avatar-in-settings", + title: "Apps, channels, and extensions/External chat and email/Connections/Slack/Avatar in Settings", component: SlackAvatarSettingsPreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/slack-communication-settings.stories.tsx b/ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx similarity index 86% rename from ui/storybook/stories/slack-communication-settings.stories.tsx rename to ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx index fa28a1145dc..7e6d39a63dc 100644 --- a/ui/storybook/stories/slack-communication-settings.stories.tsx +++ b/ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { SlackAvatarSettingsPreview } from "../prototypes/slack-avatar/SlackAvatarSettingsPreview"; +import { SlackAvatarSettingsPreview } from "../../prototypes/slack-avatar/SlackAvatarSettingsPreview"; const meta = { - title: "Connections/Slack/Communication in Settings", + id: "connections-slack-communication-in-settings", + title: "Apps, channels, and extensions/External chat and email/Connections/Slack/Communication in Settings", component: SlackAvatarSettingsPreview, parameters: { layout: "fullscreen", docs: { description: { component: "Production communication field in a Settings fixture. Saves use local story state; instructions apply to new tasks. Reply detail and progress are built-in behavior." } } }, } satisfies Meta; diff --git a/ui/storybook/stories/slack-managed-setup.stories.tsx b/ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx similarity index 96% rename from ui/storybook/stories/slack-managed-setup.stories.tsx rename to ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx index 78638174cf1..5af30cc73ad 100644 --- a/ui/storybook/stories/slack-managed-setup.stories.tsx +++ b/ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; -import { SlackSetupFixture } from "../fixtures/SlackSetupFixture"; +import { SlackSetupFixture } from "../../fixtures/SlackSetupFixture"; const start = "/PAP/apps/chat/connect?provider=slack&purpose=chat"; const meta = { - title: "Connections/Slack/Managed setup", component: SlackSetupFixture, + id: "connections-slack-managed-setup", + title: "Apps, channels, and extensions/External chat and email/Connections/Slack/Managed setup", component: SlackSetupFixture, args: { managed: true }, parameters: { layout: "fullscreen", initialEntries: [`${start}&resume=slack-story`] }, render: args => , diff --git a/ui/storybook/stories/slack-tools.stories.tsx b/ui/storybook/stories/chat-channels/slack-tools.stories.tsx similarity index 91% rename from ui/storybook/stories/slack-tools.stories.tsx rename to ui/storybook/stories/chat-channels/slack-tools.stories.tsx index 18d7b994119..c5eba3392a2 100644 --- a/ui/storybook/stories/slack-tools.stories.tsx +++ b/ui/storybook/stories/chat-channels/slack-tools.stories.tsx @@ -3,7 +3,7 @@ import { SLACK_TOOLS, type SlackSearchStatus } from "@paperclipai/shared"; import { SlackCapabilitiesView, SlackSearchView, -} from "../../src/pages/apps/chat/SlackToolSettings"; +} from "../../../src/pages/apps/chat/SlackToolSettings"; const base: SlackSearchStatus = { canConfigure: true, configured: false, @@ -52,7 +52,8 @@ function Preview({ ); } export default { - title: "Connections/Slack/Task tools", + id: "connections-slack-task-tools", + title: "Apps, channels, and extensions/External chat and email/Connections/Slack/Task tools", component: Preview, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/decision-models/01-settings.stories.tsx b/ui/storybook/stories/companies/decision-models/01-settings.stories.tsx similarity index 83% rename from ui/storybook/stories/decision-models/01-settings.stories.tsx rename to ui/storybook/stories/companies/decision-models/01-settings.stories.tsx index 51848e78396..f29a32edc68 100644 --- a/ui/storybook/stories/decision-models/01-settings.stories.tsx +++ b/ui/storybook/stories/companies/decision-models/01-settings.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { fn } from "storybook/test"; import { DecisionModelSettingsView } from "@/components/decision-models/DecisionModelSettings"; -import { choices, configured, result, unconfigured } from "./fixtures"; +import { choices, configured, result, unconfigured } from "../../../support/decision-models/fixtures"; -const meta = { title: "Decision models/01 Settings", component: DecisionModelSettingsView, +const meta = { id: "decision-models-01-settings", + title: "Getting started and access/Companies and portability/Decision models/01 Settings", component: DecisionModelSettingsView, decorators: [Story =>
], args: { settings: configured, choices, onSave: fn(), onTest: fn(), onAdd: fn() }, } satisfies Meta; diff --git a/ui/storybook/stories/decision-models/02-history.stories.tsx b/ui/storybook/stories/companies/decision-models/02-history.stories.tsx similarity index 81% rename from ui/storybook/stories/decision-models/02-history.stories.tsx rename to ui/storybook/stories/companies/decision-models/02-history.stories.tsx index 8ee9f752baa..5380d559d76 100644 --- a/ui/storybook/stories/decision-models/02-history.stories.tsx +++ b/ui/storybook/stories/companies/decision-models/02-history.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { DecisionHistoryTable } from "@/components/decision-models/DecisionHistory"; -import { entry } from "./fixtures"; +import { entry } from "../../../support/decision-models/fixtures"; -const meta = { title: "Decision models/02 History", component: DecisionHistoryTable, +const meta = { id: "decision-models-02-history", + title: "Getting started and access/Companies and portability/Decision models/02 History", component: DecisionHistoryTable, decorators: [Story =>
], args: { entries: [entry] }, } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/decision-models/03-journey.stories.tsx b/ui/storybook/stories/companies/decision-models/03-journey.stories.tsx similarity index 96% rename from ui/storybook/stories/decision-models/03-journey.stories.tsx rename to ui/storybook/stories/companies/decision-models/03-journey.stories.tsx index 8a409367d7f..2140d99c977 100644 --- a/ui/storybook/stories/decision-models/03-journey.stories.tsx +++ b/ui/storybook/stories/companies/decision-models/03-journey.stories.tsx @@ -7,7 +7,7 @@ import { Layout } from "@/components/Layout"; import { CompanySettings } from "@/pages/CompanySettings"; import { Costs } from "@/pages/Costs"; import { PluginLauncherProvider } from "@/plugins/launchers"; -import { choices, companyId, configured, entry, result, unconfigured } from "./fixtures"; +import { choices, companyId, configured, entry, result, unconfigured } from "../../../support/decision-models/fixtures"; import type { DecisionHistoryEntry } from "@paperclipai/shared"; /** Production pages and shell; only API responses and paid provider execution are simulated. */ @@ -68,7 +68,8 @@ function Journey({ alreadyConfigured = false }: { alreadyConfigured?: boolean }) } /> : null; } -const meta = { title: "Decision models/03 Journey", component: Journey, parameters: { +const meta = { id: "decision-models-03-journey", + title: "Getting started and access/Companies and portability/Decision models/03 Journey", component: Journey, parameters: { layout: "fullscreen", initialEntries: ["/PAP/company/settings"], docs: { story: { inline: false }, description: { component: "Select an existing shared connection, preserve the background setting, save, run the sample, and follow View usage. Paid calls are fixtures." } }, } } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/fast-responses/01-settings.stories.tsx b/ui/storybook/stories/companies/fast-responses/01-settings.stories.tsx similarity index 91% rename from ui/storybook/stories/fast-responses/01-settings.stories.tsx rename to ui/storybook/stories/companies/fast-responses/01-settings.stories.tsx index 4ed8d04936f..e306ab47450 100644 --- a/ui/storybook/stories/fast-responses/01-settings.stories.tsx +++ b/ui/storybook/stories/companies/fast-responses/01-settings.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { fn } from "storybook/test"; import { FastResponseSettingsView } from "@/components/fast-responses/FastResponseSettings"; -import { choices } from "../decision-models/fixtures"; +import { choices } from "../../../support/decision-models/fixtures"; import type { FastResponseSettings, FastResponseTestResult, @@ -36,7 +36,8 @@ export const result: FastResponseTestResult = { }, }; const meta = { - title: "Fast responses/01 Settings", + id: "fast-responses-01-settings", + title: "Getting started and access/Companies and portability/Fast responses/01 Settings", excludeStories: ["empty", "configured", "result"], component: FastResponseSettingsView, decorators: [ diff --git a/ui/storybook/stories/fast-responses/02-history.stories.tsx b/ui/storybook/stories/companies/fast-responses/02-history.stories.tsx similarity index 91% rename from ui/storybook/stories/fast-responses/02-history.stories.tsx rename to ui/storybook/stories/companies/fast-responses/02-history.stories.tsx index 646074f0059..e3880534fb4 100644 --- a/ui/storybook/stories/fast-responses/02-history.stories.tsx +++ b/ui/storybook/stories/companies/fast-responses/02-history.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import type { FastResponseHistoryEntry } from "@paperclipai/shared"; import { DecisionHistoryTable } from "@/components/decision-models/DecisionHistory"; -import { entry } from "../decision-models/fixtures"; +import { entry } from "../../../support/decision-models/fixtures"; const receipt: FastResponseHistoryEntry = { ...entry, feature: "message", @@ -16,7 +16,8 @@ const receipt: FastResponseHistoryEntry = { costStatus: "reported", }; const meta = { - title: "Fast responses/02 History", + id: "fast-responses-02-history", + title: "Getting started and access/Companies and portability/Fast responses/02 History", component: DecisionHistoryTable, decorators: [ (Story) => ( diff --git a/ui/storybook/stories/AppLogoRegistry.stories.tsx b/ui/storybook/stories/connection-setup/AppLogoRegistry.stories.tsx similarity index 86% rename from ui/storybook/stories/AppLogoRegistry.stories.tsx rename to ui/storybook/stories/connection-setup/AppLogoRegistry.stories.tsx index 1c002a6bab4..218ef2d7f97 100644 --- a/ui/storybook/stories/AppLogoRegistry.stories.tsx +++ b/ui/storybook/stories/connection-setup/AppLogoRegistry.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { AppLogo } from "@/pages/apps/AppLogo"; -import manifest from "../../public/brands/apps/manifest.json"; +import manifest from "../../../public/brands/apps/manifest.json"; const sizes = [24, 28, 32, 36, 44, 48]; const launchOrder = ["gmail", "google-calendar", "slack", "stripe"]; @@ -31,7 +31,8 @@ function Registry() { ); } -const meta = { title: "Apps/Canonical icon registry", component: Registry, parameters: { layout: "fullscreen" } } satisfies Meta; +const meta = { id: "apps-canonical-icon-registry", + title: "Apps, channels, and extensions/Connection setup/Apps/Canonical icon registry", component: Registry, parameters: { layout: "fullscreen" } } satisfies Meta; export default meta; type Story = StoryObj; export const Light: Story = { globals: { theme: "light" } }; diff --git a/ui/storybook/stories/ArcadeConnection.stories.tsx b/ui/storybook/stories/connection-setup/ArcadeConnection.stories.tsx similarity index 94% rename from ui/storybook/stories/ArcadeConnection.stories.tsx rename to ui/storybook/stories/connection-setup/ArcadeConnection.stories.tsx index 04688e63b62..15186c52ec4 100644 --- a/ui/storybook/stories/ArcadeConnection.stories.tsx +++ b/ui/storybook/stories/connection-setup/ArcadeConnection.stories.tsx @@ -1,9 +1,10 @@ import { userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { RemoteMcpConnectionReview } from "../prototypes/RemoteMcpConnectionReview"; +import { RemoteMcpConnectionReview } from "../../prototypes/RemoteMcpConnectionReview"; const meta = { - title: "Apps/Connections/Arcade", + id: "apps-connections-arcade", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Arcade", component: RemoteMcpConnectionReview, args: { provider: "arcade", scenario: "journey" }, parameters: { layout: "fullscreen", docs: { description: { component: "Interactive design review using application views with in-memory provider responses. No real authentication, tool calls, or credential persistence. Representative catalogs are examples; production discovery supplies the actual tools." } } }, diff --git a/ui/storybook/stories/ComposioConnection.stories.tsx b/ui/storybook/stories/connection-setup/ComposioConnection.stories.tsx similarity index 94% rename from ui/storybook/stories/ComposioConnection.stories.tsx rename to ui/storybook/stories/connection-setup/ComposioConnection.stories.tsx index fc5940e9e5c..1919775505b 100644 --- a/ui/storybook/stories/ComposioConnection.stories.tsx +++ b/ui/storybook/stories/connection-setup/ComposioConnection.stories.tsx @@ -1,9 +1,10 @@ import { userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { RemoteMcpConnectionReview } from "../prototypes/RemoteMcpConnectionReview"; +import { RemoteMcpConnectionReview } from "../../prototypes/RemoteMcpConnectionReview"; const meta = { - title: "Apps/Connections/Composio", + id: "apps-connections-composio", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Composio", component: RemoteMcpConnectionReview, args: { provider: "composio", scenario: "journey" }, parameters: { layout: "fullscreen", docs: { description: { component: "Interactive design review using application views with in-memory provider responses. No real authentication, tool calls, or credential persistence. Representative catalogs are examples; production discovery supplies the actual tools." } } }, diff --git a/ui/storybook/stories/ConnectionProviderChoice.stories.tsx b/ui/storybook/stories/connection-setup/ConnectionProviderChoice.stories.tsx similarity index 96% rename from ui/storybook/stories/ConnectionProviderChoice.stories.tsx rename to ui/storybook/stories/connection-setup/ConnectionProviderChoice.stories.tsx index 911cc7d26fc..4257a9628b1 100644 --- a/ui/storybook/stories/ConnectionProviderChoice.stories.tsx +++ b/ui/storybook/stories/connection-setup/ConnectionProviderChoice.stories.tsx @@ -9,7 +9,7 @@ import { } from "@paperclipai/shared"; import { RemoteMcpAccountChoice } from "@/features/connections/remote-mcp/RemoteMcpAccountChoice"; import { QuestionForm } from "@/components/task-chat/QuestionForm"; -import { RemoteMcpConnectionReview } from "../prototypes/RemoteMcpConnectionReview"; +import { RemoteMcpConnectionReview } from "../../prototypes/RemoteMcpConnectionReview"; function ProviderChoice({ existing = false, @@ -109,7 +109,8 @@ function ProviderChoice({ ); } const meta = { - title: "Apps/Connections/Provider choice", + id: "apps-connections-provider-choice", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Provider choice", component: ProviderChoice, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/ExecutorConnection.stories.tsx b/ui/storybook/stories/connection-setup/ExecutorConnection.stories.tsx similarity index 93% rename from ui/storybook/stories/ExecutorConnection.stories.tsx rename to ui/storybook/stories/connection-setup/ExecutorConnection.stories.tsx index c4ed3881dc0..4390599ef1a 100644 --- a/ui/storybook/stories/ExecutorConnection.stories.tsx +++ b/ui/storybook/stories/connection-setup/ExecutorConnection.stories.tsx @@ -1,9 +1,10 @@ import { userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { RemoteMcpConnectionReview } from "../prototypes/RemoteMcpConnectionReview"; +import { RemoteMcpConnectionReview } from "../../prototypes/RemoteMcpConnectionReview"; const meta = { - title: "Apps/Connections/Executor", + id: "apps-connections-executor", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Executor", component: RemoteMcpConnectionReview, args: { provider: "executor", scenario: "journey" }, parameters: { layout: "fullscreen", docs: { description: { component: "Interactive design review using application views with in-memory provider responses. No real authentication, tool calls, or credential persistence. Representative catalogs are examples; production discovery supplies the actual tools." } } }, diff --git a/ui/storybook/stories/ZapierConnection.stories.tsx b/ui/storybook/stories/connection-setup/ZapierConnection.stories.tsx similarity index 92% rename from ui/storybook/stories/ZapierConnection.stories.tsx rename to ui/storybook/stories/connection-setup/ZapierConnection.stories.tsx index 3199dca11d7..bb2d43cb3d0 100644 --- a/ui/storybook/stories/ZapierConnection.stories.tsx +++ b/ui/storybook/stories/connection-setup/ZapierConnection.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { RemoteMcpConnectionReview } from "../prototypes/RemoteMcpConnectionReview"; +import { RemoteMcpConnectionReview } from "../../prototypes/RemoteMcpConnectionReview"; const meta = { - title: "Apps/Connections/Zapier", + id: "apps-connections-zapier", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Zapier", component: RemoteMcpConnectionReview, args: { provider: "zapier", scenario: "journey" }, parameters: { layout: "fullscreen", docs: { description: { component: "Interactive design review using application views with in-memory provider responses. No real authentication, tool calls, or credential persistence. Representative catalogs are examples; production discovery supplies the actual tools." } } }, diff --git a/ui/storybook/stories/agent-setup-prompt.stories.tsx b/ui/storybook/stories/connection-setup/agent-setup-prompt.stories.tsx similarity index 97% rename from ui/storybook/stories/agent-setup-prompt.stories.tsx rename to ui/storybook/stories/connection-setup/agent-setup-prompt.stories.tsx index 8ee95576a40..ac6538d07c6 100644 --- a/ui/storybook/stories/agent-setup-prompt.stories.tsx +++ b/ui/storybook/stories/connection-setup/agent-setup-prompt.stories.tsx @@ -10,8 +10,8 @@ import { GitHubSetupPrompt } from "@/pages/apps/chat/GitHubSetupPrompt"; import { McpConfigHelpDialog } from "@/pages/tools/McpConfigHelpDialog"; import { AgentInstructions } from "@/components/routine-triggers/WebhookFields"; import { IssueContinuationHandoff } from "@/components/IssueContinuationHandoff"; -import { webhookAgentInstructions } from "../fixtures/routineTriggerWizard"; -import { storybookContinuationHandoff } from "../fixtures/paperclipData"; +import { webhookAgentInstructions } from "../../fixtures/routineTriggerWizard"; +import { storybookContinuationHandoff } from "../../fixtures/paperclipData"; const slackPrompt = buildSlackSetupPrompt(import.meta.env.VITE_PAPERCLIP_INSTANCE_URL ?? ""); const apiPrompt = `Help me connect my agent to the Paperclip API. @@ -23,7 +23,8 @@ Guide me through creating a dedicated agent API key. Store it securely in my age Configure the base URL and authentication, then verify the connection with a read-only request. Show me what worked and ask before making any changes to my company.`; const meta = { - title: "Components/Agent setup prompt", + id: "components-agent-setup-prompt", + title: "Apps, channels, and extensions/Connection setup/Components/Agent setup prompt", component: AgentSetupPrompt, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/apps-catalog-source-selection.stories.tsx b/ui/storybook/stories/connection-setup/apps-catalog-source-selection.stories.tsx similarity index 95% rename from ui/storybook/stories/apps-catalog-source-selection.stories.tsx rename to ui/storybook/stories/connection-setup/apps-catalog-source-selection.stories.tsx index a665276d96b..243d44d550d 100644 --- a/ui/storybook/stories/apps-catalog-source-selection.stories.tsx +++ b/ui/storybook/stories/connection-setup/apps-catalog-source-selection.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { ConnectorCatalogNavigation } from "../prototypes/ConnectorCatalogNavigation"; +import { ConnectorCatalogNavigation } from "../../prototypes/ConnectorCatalogNavigation"; const meta = { - title: "Apps/Catalog source selection", + id: "apps-catalog-source-selection", + title: "Apps, channels, and extensions/Connection setup/Apps/Catalog source selection", component: ConnectorCatalogNavigation, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/apps-connect-discoverability.stories.tsx b/ui/storybook/stories/connection-setup/apps-connect-discoverability.stories.tsx similarity index 97% rename from ui/storybook/stories/apps-connect-discoverability.stories.tsx rename to ui/storybook/stories/connection-setup/apps-connect-discoverability.stories.tsx index 6d8d96e1625..a0221b3cb15 100644 --- a/ui/storybook/stories/apps-connect-discoverability.stories.tsx +++ b/ui/storybook/stories/connection-setup/apps-connect-discoverability.stories.tsx @@ -58,7 +58,8 @@ function PasteConfigHost() { } const meta: Meta = { - title: "Apps/Connect discoverability (PAP-11091)", + id: "apps-connect-discoverability-pap-11091", + title: "Apps, channels, and extensions/Connection setup/Apps/Connect discoverability (PAP-11091)", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/apps-connect-name.stories.tsx b/ui/storybook/stories/connection-setup/apps-connect-name.stories.tsx similarity index 95% rename from ui/storybook/stories/apps-connect-name.stories.tsx rename to ui/storybook/stories/connection-setup/apps-connect-name.stories.tsx index c4511fd2a98..534ed375a32 100644 --- a/ui/storybook/stories/apps-connect-name.stories.tsx +++ b/ui/storybook/stories/connection-setup/apps-connect-name.stories.tsx @@ -82,7 +82,8 @@ function KeyStepHost({ customName }: { customName?: string }) { } const meta: Meta = { - title: "Apps/Connect name at create time (PAP-11283)", + id: "apps-connect-name-at-create-time-pap-11283", + title: "Apps, channels, and extensions/Connection setup/Apps/Connect name at create time (PAP-11283)", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/browser-use-settings.stories.tsx b/ui/storybook/stories/connection-setup/browser-use-settings.stories.tsx similarity index 92% rename from ui/storybook/stories/browser-use-settings.stories.tsx rename to ui/storybook/stories/connection-setup/browser-use-settings.stories.tsx index 6f1feba1f73..f8be4abe552 100644 --- a/ui/storybook/stories/browser-use-settings.stories.tsx +++ b/ui/storybook/stories/connection-setup/browser-use-settings.stories.tsx @@ -6,7 +6,7 @@ import type { ToolConnection, } from "@paperclipai/shared"; import { BrowserUseSettingsPanel } from "@/pages/apps/app-detail/BrowserUseSettingsPanel"; -import { BrowserStoryProviders, mockBrowserUse } from "../fixtures/browser-use"; +import { BrowserStoryProviders, mockBrowserUse } from "../../fixtures/browser-use"; const connection = { id: "browser-use-cloud-connection", companyId: "storybook-company", @@ -27,7 +27,8 @@ const grants = { grants: [grant("shared", "organization")], } as ConnectionGrantsResponse; const meta = { - title: "Connections/Browser Use Cloud/Settings", + id: "connections-browser-use-cloud-settings", + title: "Apps, channels, and extensions/Connection setup/Connections/Browser Use Cloud/Settings", component: BrowserUseSettingsPanel, parameters: { layout: "centered", diff --git a/ui/storybook/stories/connection-instructions.stories.tsx b/ui/storybook/stories/connection-setup/connection-instructions.stories.tsx similarity index 97% rename from ui/storybook/stories/connection-instructions.stories.tsx rename to ui/storybook/stories/connection-setup/connection-instructions.stories.tsx index ca63f1d6af7..77bb5d0c59c 100644 --- a/ui/storybook/stories/connection-instructions.stories.tsx +++ b/ui/storybook/stories/connection-setup/connection-instructions.stories.tsx @@ -13,7 +13,7 @@ import { ConnectionSetupFlow } from "@/features/connections/ConnectionSetupFlow" import { AgentConnectionInstructions } from "@/features/connections/ConnectionInstructions"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { GUIDANCE_COMPANY, GUIDANCE_CONNECTION, guidanceAgents, installConnectionGuidanceFixtures } from "../fixtures/connectionInstructions"; +import { GUIDANCE_COMPANY, GUIDANCE_CONNECTION, guidanceAgents, installConnectionGuidanceFixtures } from "../../fixtures/connectionInstructions"; const HONCHO = getConnectableAppDefinition("honcho")!.agentInstructions!.text; const NOTION = "Use Notion as the source for our published product decisions. Look up the relevant decision before proposing a change, and link to the page you used. Ask before changing a published decision. If a page is unavailable, say which context is missing and continue with the information you have."; @@ -62,7 +62,8 @@ function ConnectionInstructionsPrototype({ initialView = "connection", provider } const meta = { - title: "Design explorations/Connections/Agent instructions", + id: "design-explorations-connections-agent-instructions", + title: "Apps, channels, and extensions/Connection setup/Design explorations/Connections/Agent instructions", component: ConnectionInstructionsPrototype, args: { providedInstructions: HONCHO }, parameters: { layout: "fullscreen", docs: { description: { component: "Production Layout, ConnectionSetupFlow, AppDetail, identity/access controls, and searchable Actions list with local API fixtures. The production instructions editor saves and reloads through mocked APIs. Baseline stories omit template metadata. No provider calls." } } }, diff --git a/ui/storybook/stories/enterpret-connect-flow.stories.tsx b/ui/storybook/stories/connection-setup/enterpret-connect-flow.stories.tsx similarity index 98% rename from ui/storybook/stories/enterpret-connect-flow.stories.tsx rename to ui/storybook/stories/connection-setup/enterpret-connect-flow.stories.tsx index d051444e9c3..b75fa183255 100644 --- a/ui/storybook/stories/enterpret-connect-flow.stories.tsx +++ b/ui/storybook/stories/connection-setup/enterpret-connect-flow.stories.tsx @@ -225,7 +225,8 @@ function MethodCopyHost() { } const meta: Meta = { - title: "Apps/Enterpret connector", + id: "apps-enterpret-connector", + title: "Apps, channels, and extensions/Connection setup/Apps/Enterpret connector", parameters: { layout: "fullscreen", docs: { diff --git a/ui/storybook/stories/fireflies-pr.stories.tsx b/ui/storybook/stories/connection-setup/fireflies-pr.stories.tsx similarity index 98% rename from ui/storybook/stories/fireflies-pr.stories.tsx rename to ui/storybook/stories/connection-setup/fireflies-pr.stories.tsx index 18878d8832f..c54e23d0f62 100644 --- a/ui/storybook/stories/fireflies-pr.stories.tsx +++ b/ui/storybook/stories/connection-setup/fireflies-pr.stories.tsx @@ -13,7 +13,7 @@ import { TriggersSection } from "@/components/routine-sections/editable-sections import { RoutineDetailContext, type RoutineDetailContextValue } from "@/components/routine-sections/context"; import { Button } from "@/components/ui/button"; import { useNavigate } from "@/lib/router"; -import { WebhookReview, webhook, baseRoutine } from "../fixtures/routineWebhooks"; +import { WebhookReview, webhook, baseRoutine } from "../../fixtures/routineWebhooks"; const fireflies = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "fireflies")!; const demoSecret = "storybook-only-signing-secret"; @@ -23,7 +23,8 @@ function Frame({ children }: { children: ReactNode }) { return

PR #13890 · Production components with simulated data. No provider requests or real credentials.

{children}
; } const meta = { - title: "PR reviews/Fireflies and app webhooks", + id: "pr-reviews-fireflies-and-app-webhooks", + title: "Apps, channels, and extensions/Connection setup/PR reviews/Fireflies and app webhooks", parameters: { layout: "fullscreen", docs: { description: { component: "Coverage of PR #13890: Fireflies catalog/artwork and shared connection flow, preserved action permissions, generic webhook wizard and saved settings, and both advanced trigger editors. All network mutations are simulated. Use the toolbar to switch theme or viewport. Legacy stories only demonstrate resuming existing configurations; new webhooks use Another app or script." } } }, afterEach: ({ id }) => { document.body.dataset.firefliesStoryReady = id; }, beforeEach: () => { diff --git a/ui/storybook/stories/in-feed-connections.stories.tsx b/ui/storybook/stories/connection-setup/in-feed-connections.stories.tsx similarity index 99% rename from ui/storybook/stories/in-feed-connections.stories.tsx rename to ui/storybook/stories/connection-setup/in-feed-connections.stories.tsx index dbcff615386..ecd1ad3f3fd 100644 --- a/ui/storybook/stories/in-feed-connections.stories.tsx +++ b/ui/storybook/stories/connection-setup/in-feed-connections.stories.tsx @@ -9,7 +9,7 @@ import { ConnectionSetupFlow, ConnectionSetupCompletionScreen, AccessStep, OAuth import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { TaskChatComposer } from "@/components/task-chat/TaskChatComposer"; import { TaskChatInteractionCard } from "@/components/task-chat/TaskChatInteractionCard"; -import { storybookAgentMap, storybookAgents } from "../fixtures/paperclipData"; +import { storybookAgentMap, storybookAgents } from "../../fixtures/paperclipData"; import { useNavigate } from "@/lib/router"; import { pendingConnectionIntentInteraction as pending, @@ -37,7 +37,8 @@ const connection = { type Scenario = { email?: boolean; access?: boolean; ai?: boolean; missingAiAccount?: "anthropic" | "openai"; ownerOnly?: boolean; checking?: boolean; count?: number; loading?: boolean; loadError?: boolean; completeError?: boolean; submitting?: boolean; denied?: boolean }; const meta: Meta = { - title: "Connections/In-task connections", + id: "connections-in-task-connections", + title: "Apps, channels, and extensions/Connection setup/Connections/In-task connections", parameters: { layout: "padded" }, afterEach: ({ id }) => { document.body.dataset.inFeedStoryReady = id; }, beforeEach: ({ parameters }) => { diff --git a/ui/storybook/stories/managed-cloud-oauth-handoff.stories.tsx b/ui/storybook/stories/connection-setup/managed-cloud-oauth-handoff.stories.tsx similarity index 87% rename from ui/storybook/stories/managed-cloud-oauth-handoff.stories.tsx rename to ui/storybook/stories/connection-setup/managed-cloud-oauth-handoff.stories.tsx index 110597cc6a5..3f42764f63b 100644 --- a/ui/storybook/stories/managed-cloud-oauth-handoff.stories.tsx +++ b/ui/storybook/stories/connection-setup/managed-cloud-oauth-handoff.stories.tsx @@ -2,7 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { ManagedOAuthHandoffState } from "@/pages/apps/PaperclipCloudOAuthHandoff"; const meta: Meta = { - title: "Apps/Managed Cloud OAuth handoff", + id: "apps-managed-cloud-oauth-handoff", + title: "Apps, channels, and extensions/Connection setup/Apps/Managed Cloud OAuth handoff", component: ManagedOAuthHandoffState, parameters: { layout: "fullscreen" }, args: { diff --git a/ui/storybook/stories/memory-connectors.stories.tsx b/ui/storybook/stories/connection-setup/memory-connectors.stories.tsx similarity index 98% rename from ui/storybook/stories/memory-connectors.stories.tsx rename to ui/storybook/stories/connection-setup/memory-connectors.stories.tsx index c5eae9e3018..8585a9711c7 100644 --- a/ui/storybook/stories/memory-connectors.stories.tsx +++ b/ui/storybook/stories/connection-setup/memory-connectors.stories.tsx @@ -91,7 +91,8 @@ function MemoryReview({ provider = "mem0", scenario = "catalog" }: { provider?:
; } -const meta = { title: "Apps/Connections/Memory", component: MemoryReview, parameters: { layout: "fullscreen" } } satisfies Meta; +const meta = { id: "apps-connections-memory", + title: "Apps, channels, and extensions/Connection setup/Apps/Connections/Memory", component: MemoryReview, parameters: { layout: "fullscreen" } } satisfies Meta; export default meta; type Story = StoryObj; export const CatalogAndSetup: Story = { args: { scenario: "catalog" } }; diff --git a/ui/storybook/stories/notion-connect-flow.stories.tsx b/ui/storybook/stories/connection-setup/notion-connect-flow.stories.tsx similarity index 98% rename from ui/storybook/stories/notion-connect-flow.stories.tsx rename to ui/storybook/stories/connection-setup/notion-connect-flow.stories.tsx index 0ba848c88ac..458c2a6c49c 100644 --- a/ui/storybook/stories/notion-connect-flow.stories.tsx +++ b/ui/storybook/stories/connection-setup/notion-connect-flow.stories.tsx @@ -339,7 +339,8 @@ function VercelConnectProvenanceHost() { } const meta: Meta = { - title: "Apps/Notion MCP connect flow (PAP-16650)", + id: "apps-notion-mcp-connect-flow-pap-16650", + title: "Apps, channels, and extensions/Connection setup/Apps/Notion MCP connect flow (PAP-16650)", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/personal-connection-identity.stories.tsx b/ui/storybook/stories/connection-setup/personal-connection-identity.stories.tsx similarity index 98% rename from ui/storybook/stories/personal-connection-identity.stories.tsx rename to ui/storybook/stories/connection-setup/personal-connection-identity.stories.tsx index a2f771e637f..71619ea4e0c 100644 --- a/ui/storybook/stories/personal-connection-identity.stories.tsx +++ b/ui/storybook/stories/connection-setup/personal-connection-identity.stories.tsx @@ -152,7 +152,8 @@ function IdentitiesHarness({ } const meta: Meta = { - title: "Reviews/PAP-17835 Personal connection identity", + id: "reviews-pap-17835-personal-connection-identity", + title: "Apps, channels, and extensions/Connection setup/Reviews/PAP-17835 Personal connection identity", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/ux-labs.stories.tsx b/ui/storybook/stories/developer-labs/ux-labs.stories.tsx similarity index 94% rename from ui/storybook/stories/ux-labs.stories.tsx rename to ui/storybook/stories/developer-labs/ux-labs.stories.tsx index 1a6d0e85afb..f72ae0db93c 100644 --- a/ui/storybook/stories/ux-labs.stories.tsx +++ b/ui/storybook/stories/developer-labs/ux-labs.stories.tsx @@ -13,7 +13,8 @@ function StoryFrame({ children }: { children: React.ReactNode }) { } const meta = { - title: "UX Labs/Converted Test Pages", + id: "ux-labs-converted-test-pages", + title: "Contributor surfaces/Developer previews and diagnostic labs/UX Labs/Converted Test Pages", parameters: { docs: { description: { diff --git a/ui/storybook/stories/artifact-cards/commit.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/commit.stories.tsx similarity index 86% rename from ui/storybook/stories/artifact-cards/commit.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/commit.stories.tsx index 1927ac77f84..91c6e0cda64 100644 --- a/ui/storybook/stories/artifact-cards/commit.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/commit.stories.tsx @@ -6,10 +6,11 @@ import { identityControls, textData, diffControls, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/Commit", + id: "explorations-artifact-cards-commit", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Commit", component: CommitCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/data.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/data.stories.tsx similarity index 85% rename from ui/storybook/stories/artifact-cards/data.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/data.stories.tsx index b203310bfe1..38842f75c12 100644 --- a/ui/storybook/stories/artifact-cards/data.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/data.stories.tsx @@ -6,10 +6,11 @@ import { identityControls, textData, objectData, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/Data", + id: "explorations-artifact-cards-data", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Data", component: DataCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/document.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/document.stories.tsx similarity index 85% rename from ui/storybook/stories/artifact-cards/document.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/document.stories.tsx index 00fac027ea0..968afbd5e5b 100644 --- a/ui/storybook/stories/artifact-cards/document.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/document.stories.tsx @@ -6,10 +6,11 @@ import { identityControls, textData, numberData, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/Document", + id: "explorations-artifact-cards-document", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Document", component: DocumentCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/file.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/file.stories.tsx similarity index 87% rename from ui/storybook/stories/artifact-cards/file.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/file.stories.tsx index bf9df2f7605..5183567678d 100644 --- a/ui/storybook/stories/artifact-cards/file.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/file.stories.tsx @@ -6,10 +6,11 @@ import { identityControls, textData, objectData, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/File", + id: "explorations-artifact-cards-file", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/File", component: FileCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/image.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/image.stories.tsx similarity index 80% rename from ui/storybook/stories/artifact-cards/image.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/image.stories.tsx index e6f7c0b258e..33efe33b3c5 100644 --- a/ui/storybook/stories/artifact-cards/image.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/image.stories.tsx @@ -6,11 +6,12 @@ import { identityControls, textData, numberData, -} from "./storyConfig"; -import posterUrl from "../../fixtures/artifact-media/paper-trail.png?url"; +} from "../../../support/artifact-cards/storyConfig"; +import posterUrl from "../../../fixtures/artifact-media/paper-trail.png?url"; const meta = { - title: "Explorations/Artifact cards/Image", + id: "explorations-artifact-cards-image", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Image", component: ImageCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/link-preview.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/link-preview.stories.tsx similarity index 82% rename from ui/storybook/stories/artifact-cards/link-preview.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/link-preview.stories.tsx index 7f230a559ca..baaa04c4994 100644 --- a/ui/storybook/stories/artifact-cards/link-preview.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/link-preview.stories.tsx @@ -5,10 +5,11 @@ import { parameters, identityControls, textData, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/Link preview", + id: "explorations-artifact-cards-link-preview", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Link preview", component: LinkPreviewCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/pull-request.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/pull-request.stories.tsx similarity index 90% rename from ui/storybook/stories/artifact-cards/pull-request.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/pull-request.stories.tsx index e7cda58f736..f992cfc6f07 100644 --- a/ui/storybook/stories/artifact-cards/pull-request.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/pull-request.stories.tsx @@ -7,10 +7,11 @@ import { textData, numberData, diffControls, -} from "./storyConfig"; +} from "../../../support/artifact-cards/storyConfig"; const meta = { - title: "Explorations/Artifact cards/Pull request", + id: "explorations-artifact-cards-pull-request", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Pull request", component: PullRequestCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-cards/video.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-cards/video.stories.tsx similarity index 72% rename from ui/storybook/stories/artifact-cards/video.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-cards/video.stories.tsx index 8a1f9d3cf1d..902e01eb147 100644 --- a/ui/storybook/stories/artifact-cards/video.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-cards/video.stories.tsx @@ -5,12 +5,13 @@ import { parameters, identityControls, textData, -} from "./storyConfig"; -import posterUrl from "../../fixtures/artifact-media/paper-trail.png?url"; -import videoUrl from "../../fixtures/artifact-media/paper-trail.mp4?url"; +} from "../../../support/artifact-cards/storyConfig"; +import posterUrl from "../../../fixtures/artifact-media/paper-trail.png?url"; +import videoUrl from "../../../fixtures/artifact-media/paper-trail.mp4?url"; const meta = { - title: "Explorations/Artifact cards/Video", + id: "explorations-artifact-cards-video", + title: "Tasks and conversations/Documents, attachments, and work products/Explorations/Artifact cards/Video", component: VideoCard, decorators, parameters, diff --git a/ui/storybook/stories/artifact-upload/01-components.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-upload/01-components.stories.tsx similarity index 86% rename from ui/storybook/stories/artifact-upload/01-components.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-upload/01-components.stories.tsx index 2e33e82d78b..7e6490d781c 100644 --- a/ui/storybook/stories/artifact-upload/01-components.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-upload/01-components.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { UploadPreview } from "./UploadPreview"; -import { showFileDrag } from "./fixtures"; +import { UploadPreview } from "../../../support/artifact-upload/UploadPreview"; +import { showFileDrag } from "../../../support/artifact-upload/fixtures"; const meta = { - title: "Artifact upload/01 Artifacts pane", component: UploadPreview, + id: "artifact-upload-01-artifacts-pane", + title: "Tasks and conversations/Documents, attachments, and work products/Artifact upload/01 Artifacts pane", component: UploadPreview, parameters: { layout: "fullscreen", docs: { description: { component: "The upload icon sits in the tab header without text or a background. The entire Artifacts panel, including its header, padding, existing cards and empty space, accepts files. Drag-over stories enter through the actual header drag handler and highlight the complete visible panel, including after scrolling. Use the upload icon for one or several files. Markdown becomes a document; other files use artifact cards. Uploads are simulated in memory and reset when the story reloads. The Uploading story holds its example pending; Retry completes the sample CSV upload. No task or execution state changes." }, story: { inline: false } } }, argTypes: { state: { control: "select", options: ["empty", "populated", "uploading", "error", "long"] } }, render: (args, context) =>
, diff --git a/ui/storybook/stories/artifact-upload/02-task-journey.stories.tsx b/ui/storybook/stories/documents-artifacts/artifact-upload/02-task-journey.stories.tsx similarity index 92% rename from ui/storybook/stories/artifact-upload/02-task-journey.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifact-upload/02-task-journey.stories.tsx index 6068e2d1848..6dd1cfa0233 100644 --- a/ui/storybook/stories/artifact-upload/02-task-journey.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifact-upload/02-task-journey.stories.tsx @@ -10,8 +10,8 @@ import { ArrowLeft, Box, X } from "lucide-react"; import { PluginLauncherProvider } from "@/plugins/launchers"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { useSidebar } from "@/context/SidebarContext"; -import { UploadPreview } from "./UploadPreview"; -import { issue, showFileDrag, type PreviewState } from "./fixtures"; +import { UploadPreview } from "../../../support/artifact-upload/UploadPreview"; +import { issue, showFileDrag, type PreviewState } from "../../../support/artifact-upload/fixtures"; function TaskPage({ state = "empty", panelOpenInitially = false }: { state?: PreviewState; panelOpenInitially?: boolean }) { const { setBreadcrumbs } = useBreadcrumbs(); @@ -45,7 +45,8 @@ function Journey(props: { state?: PreviewState; panelOpenInitially?: boolean }) } const meta = { - title: "Artifact upload/02 Paused task journey", component: Journey, + id: "artifact-upload-02-paused-task-journey", + title: "Tasks and conversations/Documents, attachments, and work products/Artifact upload/02 Paused task journey", component: Journey, parameters: { layout: "fullscreen", initialEntries: ["/PAP/issues/PAP-214"], docs: { description: { component: "Start on the paused, unassigned PAP-214 placeholder in the production app shell. Upload or drop files in Artifacts, open a document in another tab, then return to Artifacts. On mobile, open Artifacts from the task header. Uses production chat bubbles, paused takeover, panel frame, desktop/mobile tabs, artifact list and document viewer. The story composes these components for review; upload persistence is simulated. No live issue is modified or resumed." }, story: { inline: false } } }, render: (args, context) => , } satisfies Meta; diff --git a/ui/storybook/stories/artifacts.stories.tsx b/ui/storybook/stories/documents-artifacts/artifacts.stories.tsx similarity index 99% rename from ui/storybook/stories/artifacts.stories.tsx rename to ui/storybook/stories/documents-artifacts/artifacts.stories.tsx index 10a8723c525..ea0cbb85d83 100644 --- a/ui/storybook/stories/artifacts.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/artifacts.stories.tsx @@ -475,7 +475,8 @@ const SELECTED_GROUP_ARTIFACTS: CompanyArtifact[] = [ ]; const meta: Meta = { - title: "Pages/Artifacts", + id: "pages-artifacts", + title: "Tasks and conversations/Documents, attachments, and work products/Pages/Artifacts", }; export default meta; diff --git a/ui/storybook/stories/csv-preview.stories.tsx b/ui/storybook/stories/documents-artifacts/csv-preview.stories.tsx similarity index 95% rename from ui/storybook/stories/csv-preview.stories.tsx rename to ui/storybook/stories/documents-artifacts/csv-preview.stories.tsx index 40dc7e2b302..2c275cef749 100644 --- a/ui/storybook/stories/csv-preview.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/csv-preview.stories.tsx @@ -6,7 +6,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { TextAttachmentPreview } from "@/components/task-side-panel/TaskAttachmentPanel"; const meta: Meta = { - title: "Components/CSV File Tab", + id: "components-csv-file-tab", + title: "Tasks and conversations/Documents, attachments, and work products/Components/CSV File Tab", component: TextAttachmentPreview, parameters: { layout: "fullscreen" }, decorators: [(Story) =>
], diff --git a/ui/storybook/stories/document-annotations.stories.tsx b/ui/storybook/stories/documents-artifacts/document-annotations.stories.tsx similarity index 99% rename from ui/storybook/stories/document-annotations.stories.tsx rename to ui/storybook/stories/documents-artifacts/document-annotations.stories.tsx index 63a18f0c6e7..deae38c99ba 100644 --- a/ui/storybook/stories/document-annotations.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/document-annotations.stories.tsx @@ -504,7 +504,8 @@ function StatesShowcase({ focusedThreadId = "open-1" }: { focusedThreadId?: stri } const meta = { - title: "Product/Documents/Annotations", + id: "product-documents-annotations", + title: "Tasks and conversations/Documents, attachments, and work products/Product/Documents/Annotations", component: StatesShowcase, parameters: { docs: { diff --git a/ui/storybook/stories/document-comments.stories.tsx b/ui/storybook/stories/documents-artifacts/document-comments.stories.tsx similarity index 97% rename from ui/storybook/stories/document-comments.stories.tsx rename to ui/storybook/stories/documents-artifacts/document-comments.stories.tsx index 0e03550df28..befef1c6b10 100644 --- a/ui/storybook/stories/document-comments.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/document-comments.stories.tsx @@ -169,7 +169,8 @@ function DocumentCommentsMatrix() { } const meta = { - title: "Product/Document Comments", + id: "product-document-comments", + title: "Tasks and conversations/Documents, attachments, and work products/Product/Document Comments", component: DocumentCommentsMatrix, parameters: { docs: { diff --git a/ui/storybook/stories/file-viewer.stories.tsx b/ui/storybook/stories/documents-artifacts/file-viewer.stories.tsx similarity index 98% rename from ui/storybook/stories/file-viewer.stories.tsx rename to ui/storybook/stories/documents-artifacts/file-viewer.stories.tsx index aa3ddaa437f..cbed714bd0e 100644 --- a/ui/storybook/stories/file-viewer.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/file-viewer.stories.tsx @@ -212,7 +212,8 @@ function buildFetchStub( } const meta: Meta = { - title: "Components/File Viewer Sheet", + id: "components-file-viewer-sheet", + title: "Tasks and conversations/Documents, attachments, and work products/Components/File Viewer Sheet", component: FileViewerSheet, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/html-artifacts/01-preview.stories.tsx b/ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx similarity index 87% rename from ui/storybook/stories/html-artifacts/01-preview.stories.tsx rename to ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx index 953713b37ae..9059baf18ce 100644 --- a/ui/storybook/stories/html-artifacts/01-preview.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { TextAttachmentPreview } from "@/components/task-side-panel/TaskAttachmentPanel"; -import { reportHtml, securityHtml } from "./fixtures"; +import { reportHtml, securityHtml } from "../../../support/html-artifacts/fixtures"; function Preview({ html = reportHtml, title = "repository-usage.html", width = 800 }: { html?: string; title?: string; width?: number }) { return
@@ -11,7 +11,8 @@ function Preview({ html = reportHtml, title = "repository-usage.html", width = 8 } const meta = { - title: "HTML artifacts/01 Preview", + id: "html-artifacts-01-preview", + title: "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview", component: Preview, args: { html: reportHtml, title: "repository-usage.html", width: 800 }, parameters: { layout: "fullscreen", docs: { description: { component: "Production attachment viewer. Inline CSS and JavaScript run in an opaque-origin sandbox. Remote dependencies and API requests are blocked. The original file is available through Download or Raw." }, story: { inline: false } } }, diff --git a/ui/storybook/stories/html-artifacts/02-workspace.stories.tsx b/ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx similarity index 84% rename from ui/storybook/stories/html-artifacts/02-workspace.stories.tsx rename to ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx index eedb1f17d0d..71e58e8b143 100644 --- a/ui/storybook/stories/html-artifacts/02-workspace.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx @@ -6,7 +6,7 @@ import { TaskWorkspaceFilePanel } from "@/components/task-side-panel/TaskWorkspa import { FileViewerSheet } from "@/components/FileViewerSheet"; import { FileViewerProvider } from "@/context/FileViewerContext"; import { queryKeys } from "@/lib/queryKeys"; -import { reportHtml } from "./fixtures"; +import { reportHtml } from "../../../support/html-artifacts/fixtures"; const issueId = "html-workspace-story"; const state = { path: "reports/repository-usage.html", workspace: "project" as const, workspaceId: null, projectId: null, line: null, column: null }; @@ -32,7 +32,8 @@ function Workspace({ sheet = false }: { sheet?: boolean }) {
; } -const meta = { title: "HTML artifacts/02 Workspace", component: Workspace, render: (args, context) => , parameters: { layout: "fullscreen", docs: { story: { inline: false } } } } satisfies Meta; +const meta = { id: "html-artifacts-02-workspace", + title: "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace", component: Workspace, render: (args, context) => , parameters: { layout: "fullscreen", docs: { story: { inline: false } } } } satisfies Meta; export default meta; type Story = StoryObj; export const TaskPanel: Story = {}; diff --git a/ui/storybook/stories/html-artifacts/03-task-journey.stories.tsx b/ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx similarity index 93% rename from ui/storybook/stories/html-artifacts/03-task-journey.stories.tsx rename to ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx index 80458df3e00..6b9df5170fb 100644 --- a/ui/storybook/stories/html-artifacts/03-task-journey.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx @@ -10,9 +10,9 @@ import { IssueArtifactFile } from "@/components/artifacts/IssueArtifactCard"; import { TextAttachmentContext } from "@/context/TextAttachmentContext"; import { PluginLauncherProvider } from "@/plugins/launchers"; import { queryKeys } from "@/lib/queryKeys"; -import { createIssue, storybookAgents } from "../../fixtures/paperclipData"; -import { reportHtml } from "./fixtures"; -import reportDownload from "./report.html?url"; +import { createIssue, storybookAgents } from "../../../fixtures/paperclipData"; +import { reportHtml } from "../../../support/html-artifacts/fixtures"; +import reportDownload from "../../../support/html-artifacts/report.html?url"; const issue = createIssue({ id: "html-artifact-journey", identifier: "DEMO-101", title: "Find popular repositories that use Paperclip a lot", status: "done", completedAt: new Date("2026-10-07T12:54:00Z"), currentExecutionWorkspace: null, executionWorkspaceId: null }); const attachment: IssueAttachment = { @@ -63,7 +63,8 @@ function Journey(props: { openInitially?: boolean }) { ; } const meta = { - title: "HTML artifacts/03 Task journey", component: Journey, + id: "html-artifacts-03-task-journey", + title: "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey", component: Journey, parameters: { layout: "fullscreen", initialEntries: ["/PAP/issues/html-artifact-journey"], docs: { description: { component: "Open the HTML artifact from the task's report card or Artifacts tab, filter the report, then switch to Raw beside Download. Uses the production app shell, artifact card, task panel and HTML renderer with simulated report data." }, story: { inline: false } } }, beforeEach: () => { const originalFetch = window.fetch; diff --git a/ui/storybook/stories/rich-work-product-cards.stories.tsx b/ui/storybook/stories/documents-artifacts/rich-work-product-cards.stories.tsx similarity index 96% rename from ui/storybook/stories/rich-work-product-cards.stories.tsx rename to ui/storybook/stories/documents-artifacts/rich-work-product-cards.stories.tsx index a85c39c499e..5127de63abc 100644 --- a/ui/storybook/stories/rich-work-product-cards.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/rich-work-product-cards.stories.tsx @@ -1,12 +1,13 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import type { IssueAttachment, IssueWorkProduct } from "@paperclipai/shared"; import { expect } from "storybook/test"; -import { RichWorkProductCard } from "../../src/components/task-chat/RichWorkProductCard"; -import { TaskChatBubble } from "../../src/components/task-chat/TaskChatBubble"; -import type { TaskChatMessageItem } from "../../src/components/task-chat/task-chat-model"; +import { RichWorkProductCard } from "../../../src/components/task-chat/RichWorkProductCard"; +import { TaskChatBubble } from "../../../src/components/task-chat/TaskChatBubble"; +import type { TaskChatMessageItem } from "../../../src/components/task-chat/task-chat-model"; const meta = { - title: "Task Chat/Rich Work Product Cards", + id: "task-chat-rich-work-product-cards", + title: "Tasks and conversations/Documents, attachments, and work products/Task Chat/Rich Work Product Cards", component: RichWorkProductCard, parameters: { layout: "centered" }, } satisfies Meta; diff --git a/ui/storybook/stories/task-artifact-gallery.stories.tsx b/ui/storybook/stories/documents-artifacts/task-artifact-gallery.stories.tsx similarity index 92% rename from ui/storybook/stories/task-artifact-gallery.stories.tsx rename to ui/storybook/stories/documents-artifacts/task-artifact-gallery.stories.tsx index 79a021486e2..f2a2cbcee71 100644 --- a/ui/storybook/stories/task-artifact-gallery.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/task-artifact-gallery.stories.tsx @@ -10,14 +10,14 @@ import { queryKeys } from "@/lib/queryKeys"; import { isImageLikeOutput, isVideoLikeOutput } from "@/lib/issue-output"; import { TaskChatBubble } from "@/components/task-chat/TaskChatBubble"; import { workProductHref } from "@/lib/issue-artifacts"; -import { createIssue, storybookAgents } from "../fixtures/paperclipData"; -import manila from "../fixtures/artifact-media/manila-ledger.mp4?url"; -import night from "../fixtures/artifact-media/night-pills.mp4?url"; -import tickets from "../fixtures/artifact-media/ticket-board.mp4?url"; -import type from "../fixtures/artifact-media/big-type.mp4?url"; -import trail from "../fixtures/artifact-media/paper-trail.mp4?url"; -import manilaImage from "../fixtures/artifact-media/manila-ledger.png?url"; -import nightImage from "../fixtures/artifact-media/night-pills.png?url"; +import { createIssue, storybookAgents } from "../../fixtures/paperclipData"; +import manila from "../../fixtures/artifact-media/manila-ledger.mp4?url"; +import night from "../../fixtures/artifact-media/night-pills.mp4?url"; +import tickets from "../../fixtures/artifact-media/ticket-board.mp4?url"; +import type from "../../fixtures/artifact-media/big-type.mp4?url"; +import trail from "../../fixtures/artifact-media/paper-trail.mp4?url"; +import manilaImage from "../../fixtures/artifact-media/manila-ledger.png?url"; +import nightImage from "../../fixtures/artifact-media/night-pills.png?url"; const issue = createIssue({ id: "artifact-gallery-story", identifier: "DEMO-101", title: "Explore Paperclip Ships styles", status: "done" }); const date = new Date("2026-09-22T18:03:00Z"); @@ -93,7 +93,8 @@ function GalleryStory({ scenario = "videos", width = 480 }: { scenario?: Scenari ); } const meta = { - title: "Tasks/Artifact Gallery", component: GalleryStory, parameters: { layout: "fullscreen" }, + id: "tasks-artifact-gallery", + title: "Tasks and conversations/Documents, attachments, and work products/Tasks/Artifact Gallery", component: GalleryStory, parameters: { layout: "fullscreen" }, args: { scenario: "videos", width: 480 }, beforeEach: ({ args }) => { const originalFetch = window.fetch; diff --git a/ui/storybook/stories/task-text-files.stories.tsx b/ui/storybook/stories/documents-artifacts/task-text-files.stories.tsx similarity index 91% rename from ui/storybook/stories/task-text-files.stories.tsx rename to ui/storybook/stories/documents-artifacts/task-text-files.stories.tsx index e1db636b869..7e0c73ec666 100644 --- a/ui/storybook/stories/task-text-files.stories.tsx +++ b/ui/storybook/stories/documents-artifacts/task-text-files.stories.tsx @@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { IssueAttachment } from "@paperclipai/shared"; import { TaskSidePanel } from "@/components/task-side-panel/TaskSidePanel"; import { queryKeys } from "@/lib/queryKeys"; -import { createIssue } from "../fixtures/paperclipData"; +import { createIssue } from "../../fixtures/paperclipData"; const markdown = "# Account review charter\n\nInvestigate whether new and active accounts succeeded.\n\n## Rules\n\n- Read customer data only.\n- Keep private information out of reports.\n- Report failures with evidence.\n\n```text\nReview → Evidence → Report\n```"; const plain = "Daily review summary\n\nAccounts reviewed: 20\nCompleted successfully: 18\nNeeds investigation: 2\n\n"; @@ -40,7 +40,8 @@ function TextFilesStory({ initial = "list", width = 640 }: { initial?: "list" | ; } -const meta = { title: "Tasks/Text file tabs", component: TextFilesStory, parameters: { layout: "fullscreen" }, render: (args, context) => } satisfies Meta; +const meta = { id: "tasks-text-file-tabs", + title: "Tasks and conversations/Documents, attachments, and work products/Tasks/Text file tabs", component: TextFilesStory, parameters: { layout: "fullscreen" }, render: (args, context) => } satisfies Meta; export default meta; type Story = StoryObj; export const OpenFromArtifacts: Story = {}; diff --git a/ui/storybook/stories/forms-editors.stories.tsx b/ui/storybook/stories/foundations/forms-editors.stories.tsx similarity index 99% rename from ui/storybook/stories/forms-editors.stories.tsx rename to ui/storybook/stories/foundations/forms-editors.stories.tsx index 7f1a723acda..e418820fd13 100644 --- a/ui/storybook/stories/forms-editors.stories.tsx +++ b/ui/storybook/stories/foundations/forms-editors.stories.tsx @@ -20,7 +20,7 @@ import { ScheduleEditor, describeSchedule } from "@/components/ScheduleEditor"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { buildExecutionPolicy } from "@/lib/issue-execution-policy"; -import { createIssue, storybookAgents } from "../fixtures/paperclipData"; +import { createIssue, storybookAgents } from "../../fixtures/paperclipData"; function Section({ eyebrow, @@ -712,7 +712,8 @@ function RoutineRunDialogStory() { } const meta = { - title: "Components/Forms & Editors", + id: "components-forms-editors", + title: "Foundations/Components/Forms & Editors", parameters: { docs: { description: { diff --git a/ui/storybook/stories/foundations.stories.tsx b/ui/storybook/stories/foundations/foundations.stories.tsx similarity index 99% rename from ui/storybook/stories/foundations.stories.tsx rename to ui/storybook/stories/foundations/foundations.stories.tsx index 94c1a6874e4..86d1c6eb810 100644 --- a/ui/storybook/stories/foundations.stories.tsx +++ b/ui/storybook/stories/foundations/foundations.stories.tsx @@ -281,6 +281,7 @@ function FoundationsMatrix() { } const meta = { + id: "foundations-primitive-matrix", title: "Foundations/Primitive Matrix", component: FoundationsMatrix, parameters: { diff --git a/ui/storybook/stories/primitives-coverage.stories.tsx b/ui/storybook/stories/foundations/primitives-coverage.stories.tsx similarity index 99% rename from ui/storybook/stories/primitives-coverage.stories.tsx rename to ui/storybook/stories/foundations/primitives-coverage.stories.tsx index 164f97f9361..98d99b099ad 100644 --- a/ui/storybook/stories/primitives-coverage.stories.tsx +++ b/ui/storybook/stories/foundations/primitives-coverage.stories.tsx @@ -69,6 +69,7 @@ import { Skeleton } from "@/components/ui/skeleton"; * static, deterministic state so the visual snapshot suite baselines it. */ const meta = { + id: "foundations-primitive-coverage", title: "Foundations/Primitive Coverage", parameters: { docs: { diff --git a/ui/storybook/stories/status-language.stories.tsx b/ui/storybook/stories/foundations/status-language.stories.tsx similarity index 99% rename from ui/storybook/stories/status-language.stories.tsx rename to ui/storybook/stories/foundations/status-language.stories.tsx index b39ab4b99c5..82d356b9fb8 100644 --- a/ui/storybook/stories/status-language.stories.tsx +++ b/ui/storybook/stories/foundations/status-language.stories.tsx @@ -17,7 +17,7 @@ import { QuotaBar } from "@/components/QuotaBar"; import { StatusBadge } from "@/components/StatusBadge"; import { StatusIcon } from "@/components/StatusIcon"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { createIssue } from "../fixtures/paperclipData"; +import { createIssue } from "../../fixtures/paperclipData"; function Section({ eyebrow, @@ -589,6 +589,7 @@ function StatusLanguage() { } const meta = { + id: "foundations-status-language", title: "Foundations/Status Language", component: StatusLanguage, parameters: { diff --git a/ui/storybook/stories/access-profiles.stories.tsx b/ui/storybook/stories/gateways-profiles/access-profiles.stories.tsx similarity index 99% rename from ui/storybook/stories/access-profiles.stories.tsx rename to ui/storybook/stories/gateways-profiles/access-profiles.stories.tsx index 4ad365bdd65..c236a5bfae0 100644 --- a/ui/storybook/stories/access-profiles.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/access-profiles.stories.tsx @@ -474,7 +474,8 @@ function SeededAgentTools() { } const meta: Meta = { - title: "Tools/Access profiles", + id: "tools-access-profiles", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Tools/Access profiles", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/gateways-tab.stories.tsx b/ui/storybook/stories/gateways-profiles/gateways-tab.stories.tsx similarity index 98% rename from ui/storybook/stories/gateways-tab.stories.tsx rename to ui/storybook/stories/gateways-profiles/gateways-tab.stories.tsx index 1a24d272db1..1eac81d5aa1 100644 --- a/ui/storybook/stories/gateways-tab.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/gateways-tab.stories.tsx @@ -242,7 +242,8 @@ function GatewaysHost({ gateways }: { gateways: ToolMcpGatewayWithTokens[] }) { } const meta: Meta = { - title: "Tools/Gateways (PAP-11182)", + id: "tools-gateways-pap-11182", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Tools/Gateways (PAP-11182)", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/public-mcp-connections.stories.tsx b/ui/storybook/stories/gateways-profiles/public-mcp-connections.stories.tsx similarity index 90% rename from ui/storybook/stories/public-mcp-connections.stories.tsx rename to ui/storybook/stories/gateways-profiles/public-mcp-connections.stories.tsx index fd6623e7599..0f45100710d 100644 --- a/ui/storybook/stories/public-mcp-connections.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/public-mcp-connections.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, waitFor, within } from "storybook/test"; import { AssistantConnectionsPage } from "@/pages/McpConnect"; -import { installPublicMcpFixture } from "../fixtures/publicMcp"; +import { installPublicMcpFixture } from "../../fixtures/publicMcp"; const meta = { - title: "Assistant connections/Manage connections", component: AssistantConnectionsPage, + id: "assistant-connections-manage-connections", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Assistant connections/Manage connections", component: AssistantConnectionsPage, parameters: { layout: "padded", initialEntries: ["/assistant-connections"], docs: { description: { component: "Production /assistant-connections page. Revocation changes only the in-memory story fixture." } } }, beforeEach: ({ parameters }) => installPublicMcpFixture(parameters.fixture), } satisfies Meta; diff --git a/ui/storybook/stories/public-mcp-consent.stories.tsx b/ui/storybook/stories/gateways-profiles/public-mcp-consent.stories.tsx similarity index 98% rename from ui/storybook/stories/public-mcp-consent.stories.tsx rename to ui/storybook/stories/gateways-profiles/public-mcp-consent.stories.tsx index 5a305fbfac0..543046702af 100644 --- a/ui/storybook/stories/public-mcp-consent.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/public-mcp-consent.stories.tsx @@ -2,10 +2,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; import { Route, Routes } from "@/lib/router"; import { McpConnectPage, AssistantConnectionsPage } from "@/pages/McpConnect"; -import { consentSubmission, installPublicMcpFixture, request } from "../fixtures/publicMcp"; +import { consentSubmission, installPublicMcpFixture, request } from "../../fixtures/publicMcp"; const meta = { - title: "Assistant connections/Consent", + id: "assistant-connections-consent", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Assistant connections/Consent", component: McpConnectPage, parameters: { layout: "padded", initialEntries: [`/mcp-connect/${request.id}`], docs: { description: { component: "Production consent page at /mcp-connect/:id. API fixtures are isolated. Successful consent stays in this preview; no OAuth credentials are issued." } } }, beforeEach: ({ parameters }) => installPublicMcpFixture(parameters.fixture), diff --git a/ui/storybook/stories/public-mcp-device.stories.tsx b/ui/storybook/stories/gateways-profiles/public-mcp-device.stories.tsx similarity index 88% rename from ui/storybook/stories/public-mcp-device.stories.tsx rename to ui/storybook/stories/gateways-profiles/public-mcp-device.stories.tsx index 93b92b0891c..758bf5e7d3c 100644 --- a/ui/storybook/stories/public-mcp-device.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/public-mcp-device.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; import { McpDevicePage } from "@/pages/McpConnect"; -import { installPublicMcpFixture } from "../fixtures/publicMcp"; +import { installPublicMcpFixture } from "../../fixtures/publicMcp"; const meta = { - title: "Assistant connections/Device authorization", component: McpDevicePage, + id: "assistant-connections-device-authorization", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Assistant connections/Device authorization", component: McpDevicePage, parameters: { layout: "padded", fixture: {}, docs: { description: { component: "Production device consent. The assistant's private device code stays in its credential client; only the human verification code appears here. These isolated fixtures issue no credentials." } } }, args: { initialCode: "BCDF-GHJK" }, beforeEach: ({ parameters }) => installPublicMcpFixture(parameters.fixture), diff --git a/ui/storybook/stories/public-mcp-journey.stories.tsx b/ui/storybook/stories/gateways-profiles/public-mcp-journey.stories.tsx similarity index 94% rename from ui/storybook/stories/public-mcp-journey.stories.tsx rename to ui/storybook/stories/gateways-profiles/public-mcp-journey.stories.tsx index a4c43830ffb..9b4a481e1ad 100644 --- a/ui/storybook/stories/public-mcp-journey.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/public-mcp-journey.stories.tsx @@ -6,8 +6,8 @@ import { Link, Route, Routes } from "@/lib/router"; import { Browse } from "@/pages/apps/Browse"; import { AssistantConnection } from "@/pages/apps/AssistantConnection"; import { InstanceExperimentalSettings } from "@/pages/InstanceExperimentalSettings"; -import { PublicMcpPresenter } from "./public-mcp-presenter"; -import { connectedUser, installPublicMcpFixture } from "../fixtures/publicMcp"; +import { PublicMcpPresenter } from "../../support/public-mcp-presenter"; +import { connectedUser, installPublicMcpFixture } from "../../fixtures/publicMcp"; function ConnectionJourney({ assistant = "opencode" }: { assistant?: "codex" | "claude" | "opencode" | "other" }) { return @@ -21,7 +21,8 @@ function ConnectionJourney({ assistant = "opencode" }: { assistant?: "codex" | " } const meta = { - title: "Assistant connections/Start here", + id: "assistant-connections-start-here", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Assistant connections/Start here", component: ConnectionJourney, parameters: { layout: "fullscreen", initialEntries: ["/PAP/apps"], fixture: { enabled: false, empty: true }, diff --git a/ui/storybook/stories/public-mcp-setting.stories.tsx b/ui/storybook/stories/gateways-profiles/public-mcp-setting.stories.tsx similarity index 90% rename from ui/storybook/stories/public-mcp-setting.stories.tsx rename to ui/storybook/stories/gateways-profiles/public-mcp-setting.stories.tsx index a6cb3716cc1..b0da6f0b013 100644 --- a/ui/storybook/stories/public-mcp-setting.stories.tsx +++ b/ui/storybook/stories/gateways-profiles/public-mcp-setting.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; import { InstanceExperimentalSettings } from "@/pages/InstanceExperimentalSettings"; -import { installPublicMcpFixture } from "../fixtures/publicMcp"; +import { installPublicMcpFixture } from "../../fixtures/publicMcp"; const meta = { - title: "Assistant connections/Experimental setting", component: InstanceExperimentalSettings, + id: "assistant-connections-experimental-setting", + title: "Apps, channels, and extensions/Tool gateways and access profiles/Assistant connections/Experimental setting", component: InstanceExperimentalSettings, parameters: { layout: "padded", initialEntries: ["/PAP/company/settings/instance/experimental"], docs: { description: { component: "The production Experimental page, including surrounding settings. Changes are local to this story." } } }, beforeEach: ({ parameters }) => installPublicMcpFixture(parameters.fixture), } satisfies Meta; diff --git a/ui/storybook/stories/blocked-inbox.stories.tsx b/ui/storybook/stories/inbox-search/blocked-inbox.stories.tsx similarity index 98% rename from ui/storybook/stories/blocked-inbox.stories.tsx rename to ui/storybook/stories/inbox-search/blocked-inbox.stories.tsx index b4a5841cd2d..727d3ce83a7 100644 --- a/ui/storybook/stories/blocked-inbox.stories.tsx +++ b/ui/storybook/stories/inbox-search/blocked-inbox.stories.tsx @@ -6,7 +6,7 @@ import { BlockedInboxView } from "@/components/BlockedInboxView"; import { BlockedReasonChip } from "@/components/BlockedReasonChip"; import { defaultIssueFilterState } from "@/lib/issue-filters"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookIssues } from "../fixtures/paperclipData"; +import { storybookIssues } from "../../fixtures/paperclipData"; const companyId = "company-storybook"; const blockedViewDefaults = { @@ -267,7 +267,8 @@ function BlockedTabEmptyState() { } const meta = { - title: "Product/Inbox/Blocked tab", + id: "product-inbox-blocked-tab", + title: "Tasks and conversations/Inbox, decisions, and search/Product/Inbox/Blocked tab", component: BlockedTabSurface, parameters: { docs: { diff --git a/ui/storybook/stories/decision-card.stories.tsx b/ui/storybook/stories/inbox-search/decision-card.stories.tsx similarity index 98% rename from ui/storybook/stories/decision-card.stories.tsx rename to ui/storybook/stories/inbox-search/decision-card.stories.tsx index 6e051ad72d5..fa96bd89ed3 100644 --- a/ui/storybook/stories/decision-card.stories.tsx +++ b/ui/storybook/stories/inbox-search/decision-card.stories.tsx @@ -91,7 +91,8 @@ const shared = { }; const meta: Meta = { - title: "Decisions/DecisionCard", + id: "decisions-decisioncard", + title: "Tasks and conversations/Inbox, decisions, and search/Decisions/DecisionCard", component: DecisionCard, render: (args) => (
diff --git a/ui/storybook/stories/decisions-desk.stories.tsx b/ui/storybook/stories/inbox-search/decisions-desk.stories.tsx similarity index 99% rename from ui/storybook/stories/decisions-desk.stories.tsx rename to ui/storybook/stories/inbox-search/decisions-desk.stories.tsx index a2faf41e8f2..8f8645dd134 100644 --- a/ui/storybook/stories/decisions-desk.stories.tsx +++ b/ui/storybook/stories/inbox-search/decisions-desk.stories.tsx @@ -444,7 +444,8 @@ const INTERACTION_AUDIENCE_ITEMS: AttentionItem[] = [ ]; const meta: Meta = { - title: "Pages/Decisions Desk", + id: "pages-decisions-desk", + title: "Tasks and conversations/Inbox, decisions, and search/Pages/Decisions Desk", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/search.stories.tsx b/ui/storybook/stories/inbox-search/search.stories.tsx similarity index 99% rename from ui/storybook/stories/search.stories.tsx rename to ui/storybook/stories/inbox-search/search.stories.tsx index 4a3291df9a5..0d261a95416 100644 --- a/ui/storybook/stories/search.stories.tsx +++ b/ui/storybook/stories/inbox-search/search.stories.tsx @@ -39,7 +39,7 @@ import { CommandSeparator, } from "@/components/ui/command"; import { StatusBadge } from "@/components/StatusBadge"; -import { storybookAgents, storybookProjects, storybookIssues } from "../fixtures/paperclipData"; +import { storybookAgents, storybookProjects, storybookIssues } from "../../fixtures/paperclipData"; const agentsById = new Map(storybookAgents.map((agent) => [agent.id, agent])); @@ -716,7 +716,8 @@ function SearchStories() { } const meta = { - title: "Product/Search & Command K", + id: "product-search-command-k", + title: "Tasks and conversations/Inbox, decisions, and search/Product/Search & Command K", component: SearchStories, parameters: { docs: { diff --git a/ui/storybook/stories/what-needs-me.stories.tsx b/ui/storybook/stories/inbox-search/what-needs-me.stories.tsx similarity index 99% rename from ui/storybook/stories/what-needs-me.stories.tsx rename to ui/storybook/stories/inbox-search/what-needs-me.stories.tsx index fca76fdc23d..292e363df0c 100644 --- a/ui/storybook/stories/what-needs-me.stories.tsx +++ b/ui/storybook/stories/inbox-search/what-needs-me.stories.tsx @@ -488,7 +488,8 @@ function Queue({ } const meta: Meta = { - title: "Pages/Decisions", + id: "pages-decisions", + title: "Tasks and conversations/Inbox, decisions, and search/Pages/Decisions", component: Queue, parameters: { layout: "fullscreen" }, }; diff --git a/ui/storybook/stories/dev-ops-surfaces.stories.tsx b/ui/storybook/stories/instance-operations/dev-ops-surfaces.stories.tsx similarity index 95% rename from ui/storybook/stories/dev-ops-surfaces.stories.tsx rename to ui/storybook/stories/instance-operations/dev-ops-surfaces.stories.tsx index 6690392d15d..2bf35725d25 100644 --- a/ui/storybook/stories/dev-ops-surfaces.stories.tsx +++ b/ui/storybook/stories/instance-operations/dev-ops-surfaces.stories.tsx @@ -54,7 +54,8 @@ function DevOpsSurfacesStory() { } const meta = { - title: "Product/Dev Ops Surfaces", + id: "product-dev-ops-surfaces", + title: "Oversight and operation/Instance operations/Product/Dev Ops Surfaces", component: DevOpsSurfacesStory, parameters: { docs: { diff --git a/ui/storybook/stories/announcement-card.stories.tsx b/ui/storybook/stories/navigation-preferences/announcement-card.stories.tsx similarity index 93% rename from ui/storybook/stories/announcement-card.stories.tsx rename to ui/storybook/stories/navigation-preferences/announcement-card.stories.tsx index b684e28107d..5c0a53dba44 100644 --- a/ui/storybook/stories/announcement-card.stories.tsx +++ b/ui/storybook/stories/navigation-preferences/announcement-card.stories.tsx @@ -3,7 +3,8 @@ import { AnnouncementCard } from "@/components/AnnouncementCard"; import { announcementPreview, announcementAnimationPreview, announcementAnimationPreviewSrc } from "@/lib/announcement-preview"; const meta = { - title: "Announcements/AnnouncementCard", + id: "announcements-announcementcard", + title: "Oversight and operation/Navigation, profile, and announcements/Announcements/AnnouncementCard", component: AnnouncementCard, args: { announcement: announcementPreview, imageSrc: "/announcement-preview.svg", onDismiss: () => {} }, decorators: [(Story) =>
], diff --git a/ui/storybook/stories/mobile-task-tabs.stories.tsx b/ui/storybook/stories/navigation-preferences/mobile-task-tabs.stories.tsx similarity index 97% rename from ui/storybook/stories/mobile-task-tabs.stories.tsx rename to ui/storybook/stories/navigation-preferences/mobile-task-tabs.stories.tsx index b2f96dc95cd..39c5e0cc20b 100644 --- a/ui/storybook/stories/mobile-task-tabs.stories.tsx +++ b/ui/storybook/stories/navigation-preferences/mobile-task-tabs.stories.tsx @@ -48,7 +48,8 @@ function Prototype({ initialTabs = basicTabs, activeTabId = "plan" }: { initialT } const meta = { - title: "Prototypes/Task detail/Mobile tabs", + id: "prototypes-task-detail-mobile-tabs", + title: "Oversight and operation/Navigation, profile, and announcements/Prototypes/Task detail/Mobile tabs", parameters: { layout: "fullscreen", docs: { description: { component: "A task toolbar with a vertical open-tab overview. The current document stays readable; titles wrap in the overview, selection is explicit, close actions stay visible, and controls have 44px touch targets. Inspired by Apple’s toolbar and safe-area guidance: https://developer.apple.com/design/human-interface-guidelines/toolbars and https://developer.apple.com/design/human-interface-guidelines/layout." } }, diff --git a/ui/storybook/stories/navigation-layout.stories.tsx b/ui/storybook/stories/navigation-preferences/navigation-layout.stories.tsx similarity index 98% rename from ui/storybook/stories/navigation-layout.stories.tsx rename to ui/storybook/stories/navigation-preferences/navigation-layout.stories.tsx index 4eca73816f6..05316942295 100644 --- a/ui/storybook/stories/navigation-layout.stories.tsx +++ b/ui/storybook/stories/navigation-preferences/navigation-layout.stories.tsx @@ -39,7 +39,7 @@ import { storybookIssues, storybookProjects, storybookSidebarBadges, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; function Section({ eyebrow, @@ -332,7 +332,8 @@ function NavigationLayoutStories() { } const meta = { - title: "Product/Navigation & Layout", + id: "product-navigation-layout", + title: "Oversight and operation/Navigation, profile, and announcements/Product/Navigation & Layout", component: NavigationLayoutStories, // Sidebar mounts PluginLauncherOutlet unconditionally once a company is // selected; without this provider the story raced company selection and diff --git a/ui/storybook/stories/onboarding-agent-arc.stories.tsx b/ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx similarity index 99% rename from ui/storybook/stories/onboarding-agent-arc.stories.tsx rename to ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx index 8671277b425..47189737929 100644 --- a/ui/storybook/stories/onboarding-agent-arc.stories.tsx +++ b/ui/storybook/stories/onboarding/onboarding-agent-arc.stories.tsx @@ -12,11 +12,11 @@ import { STORYBOOK_COMPANY_ID, clearOnboardingDraft, seedOnboardingDraft, -} from "../fixtures/onboardingDraft"; +} from "../../fixtures/onboardingDraft"; import { resetOnboardingFixtureState, setOnboardingFixtureState, -} from "../fixtures/onboardingEnvironment"; +} from "../../fixtures/onboardingEnvironment"; /** * The onboarding wizard's agent arc: create the agent, connect a model, review. @@ -32,7 +32,8 @@ import { * reached at all. */ const meta = { - title: "Onboarding/Agent arc", + id: "onboarding-agent-arc", + title: "Getting started and access/Onboarding and first work/Onboarding/Agent arc", parameters: { layout: "centered" }, } satisfies Meta; diff --git a/ui/storybook/stories/onboarding-character.stories.tsx b/ui/storybook/stories/onboarding/onboarding-character.stories.tsx similarity index 90% rename from ui/storybook/stories/onboarding-character.stories.tsx rename to ui/storybook/stories/onboarding/onboarding-character.stories.tsx index ca6d8c462e6..b69cce348da 100644 --- a/ui/storybook/stories/onboarding-character.stories.tsx +++ b/ui/storybook/stories/onboarding/onboarding-character.stories.tsx @@ -1,11 +1,12 @@ import { useState, type ReactNode } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { AGENT_PALETTE_IDS, appearanceForPalette, type AgentPaletteId } from "@paperclipai/shared"; -import { OnboardingCharacter } from "../../src/components/onboarding/OnboardingCharacter"; -import { Button } from "../../src/components/ui/button"; +import { OnboardingCharacter } from "../../../src/components/onboarding/OnboardingCharacter"; +import { Button } from "../../../src/components/ui/button"; const meta = { - title: "Onboarding/Character", + id: "onboarding-character", + title: "Getting started and access/Onboarding and first work/Onboarding/Character", component: OnboardingCharacter, args: { appearance: appearanceForPalette("bubblegum-sky"), awake: false }, parameters: { docs: { description: { component: "The onboarding hero: the persona character (the studio's App export) on the shared ClipLab v0.2.0 engine. Gray and dozing until Review, where it plays the one-shot sleepy → wink → idle transition while its palette fades in, then settles into the idle loop. Reduced motion skips the sequence." } } }, diff --git a/ui/storybook/stories/onboarding-connect-model.stories.tsx b/ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx similarity index 95% rename from ui/storybook/stories/onboarding-connect-model.stories.tsx rename to ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx index b3c15e07399..065bc194575 100644 --- a/ui/storybook/stories/onboarding-connect-model.stories.tsx +++ b/ui/storybook/stories/onboarding/onboarding-connect-model.stories.tsx @@ -18,7 +18,8 @@ import { ConnectModelPreview } from "@/components/onboarding/ConnectModelPreview * it reads as one row changing its terms. */ const meta = { - title: "Onboarding/Connect a model", + id: "onboarding-connect-a-model", + title: "Getting started and access/Onboarding and first work/Onboarding/Connect a model", parameters: { layout: "centered" }, } satisfies Meta; diff --git a/ui/storybook/stories/overview.stories.tsx b/ui/storybook/stories/overview/overview.stories.tsx similarity index 97% rename from ui/storybook/stories/overview.stories.tsx rename to ui/storybook/stories/overview/overview.stories.tsx index c7e1ab8f647..4da748cfce2 100644 --- a/ui/storybook/stories/overview.stories.tsx +++ b/ui/storybook/stories/overview/overview.stories.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; -import catalog from "../../../maps/storybook-map/catalog.json"; +import catalog from "../../../../maps/storybook-map/catalog.json"; type IndexEntry = { id: string; type: string; importPath: string }; @@ -83,6 +83,7 @@ function StorybookGuide() { } const meta = { + id: "overview-storybook-guide", title: "Overview/Storybook Guide", component: StorybookGuide, parameters: { docs: { description: { component: "Feature-based navigation from the downstream catalog, with separate product, component, and fixture classifications. Story links come from this build's index." } } }, diff --git a/ui/storybook/stories/new-project-repos.stories.tsx b/ui/storybook/stories/projects/new-project-repos.stories.tsx similarity index 92% rename from ui/storybook/stories/new-project-repos.stories.tsx rename to ui/storybook/stories/projects/new-project-repos.stories.tsx index 3d3bc78466f..74179ac965e 100644 --- a/ui/storybook/stories/new-project-repos.stories.tsx +++ b/ui/storybook/stories/projects/new-project-repos.stories.tsx @@ -1,11 +1,12 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { NewProjectPrototype } from "../prototypes/project-repos/ProjectReposPrototype"; +import { NewProjectPrototype } from "../../prototypes/project-repos/ProjectReposPrototype"; -import { crowdedRepoIds, reviewViewports } from "../prototypes/project-repos/fixtures"; +import { crowdedRepoIds, reviewViewports } from "../../prototypes/project-repos/fixtures"; const meta = { - title: "Proposals/Project repos/New project", + id: "proposals-project-repos-new-project", + title: "Projects and execution/Projects and repositories/Proposals/Project repos/New project", component: NewProjectPrototype, tags: ["!autodocs"], parameters: { layout: "fullscreen", viewport: { options: reviewViewports }, docs: { description: { component: "Interactive design proposal only. The project name starts empty and focused, with an outlined folder icon. The source repo region scrolls while the title, name, and actions remain visible. GitHub setup uses the existing ConnectionSetupFlow; Continue to GitHub simulates a successful provider return. Creation and repository changes stay in memory." } } }, diff --git a/ui/storybook/stories/project-execution-workspace-strategy.stories.tsx b/ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx similarity index 92% rename from ui/storybook/stories/project-execution-workspace-strategy.stories.tsx rename to ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx index c47bd8ed7a6..2cfdbe3effb 100644 --- a/ui/storybook/stories/project-execution-workspace-strategy.stories.tsx +++ b/ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx @@ -4,7 +4,7 @@ import { useQueryClient } from "@tanstack/react-query"; import type { Project } from "@paperclipai/shared"; import { ProjectProperties, type ProjectConfigFieldKey, type ProjectFieldSaveState } from "@/components/ProjectProperties"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookProjects } from "../fixtures/paperclipData"; +import { storybookProjects } from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const boardProject = storybookProjects.find((project) => project.id === "project-board-ui") ?? storybookProjects[0]!; @@ -28,7 +28,8 @@ function Hydrate({ children }: { children: ReactNode }) { } const meta: Meta = { - title: "Workspaces/Project runtime provision command", + id: "workspaces-project-runtime-provision-command", + title: "Projects and execution/Projects and repositories/Workspaces/Project runtime provision command", component: ProjectProperties, parameters: { layout: "padded" }, }; diff --git a/ui/storybook/stories/project-repos-configuration.stories.tsx b/ui/storybook/stories/projects/project-repos-configuration.stories.tsx similarity index 87% rename from ui/storybook/stories/project-repos-configuration.stories.tsx rename to ui/storybook/stories/projects/project-repos-configuration.stories.tsx index 6015a5475a3..1149d465441 100644 --- a/ui/storybook/stories/project-repos-configuration.stories.tsx +++ b/ui/storybook/stories/projects/project-repos-configuration.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ProjectConfigurationPrototype } from "../prototypes/project-repos/ProjectConfigurationPage"; +import { ProjectConfigurationPrototype } from "../../prototypes/project-repos/ProjectConfigurationPage"; -import { crowdedRepoIds, reviewViewports } from "../prototypes/project-repos/fixtures"; +import { crowdedRepoIds, reviewViewports } from "../../prototypes/project-repos/fixtures"; const meta = { - title: "Proposals/Project repos/Configuration", + id: "proposals-project-repos-configuration", + title: "Projects and execution/Projects and repositories/Proposals/Project repos/Configuration", component: ProjectConfigurationPrototype, tags: ["!autodocs"], parameters: { layout: "fullscreen", viewport: { options: reviewViewports }, docs: { description: { component: "Full Onboarding configuration page, based on the Bull staging reference: sidebar, header, tabs, actual ProjectProperties, and the proposed source repo editor in the Codebase slot. Add/remove repos, save or discard, connect another GitHub account, and preserve existing text URLs. All writes and provider sign-in are simulated in Storybook." } } }, diff --git a/ui/storybook/stories/projects-goals-workspaces.stories.tsx b/ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx similarity index 99% rename from ui/storybook/stories/projects-goals-workspaces.stories.tsx rename to ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx index c10f5958366..7eb9e5fbd2f 100644 --- a/ui/storybook/stories/projects-goals-workspaces.stories.tsx +++ b/ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx @@ -27,7 +27,7 @@ import { storybookIssues, storybookProjectWorkspaces, storybookProjects, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const boardProject = storybookProjects.find((project) => project.id === "project-board-ui") ?? storybookProjects[0]!; @@ -497,7 +497,8 @@ function ProjectsGoalsWorkspacesStories() { } const meta = { - title: "Product/Projects Goals Workspaces", + id: "product-projects-goals-workspaces", + title: "Projects and execution/Projects and repositories/Product/Projects Goals Workspaces", component: ProjectsGoalsWorkspacesStories, parameters: { docs: { diff --git a/ui/storybook/stories/agent-chat-unanswered-questions.stories.tsx b/ui/storybook/stories/questions-and-approvals/agent-chat-unanswered-questions.stories.tsx similarity index 93% rename from ui/storybook/stories/agent-chat-unanswered-questions.stories.tsx rename to ui/storybook/stories/questions-and-approvals/agent-chat-unanswered-questions.stories.tsx index 6d25d088789..f292721632b 100644 --- a/ui/storybook/stories/agent-chat-unanswered-questions.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/agent-chat-unanswered-questions.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; -import { QuestionChat, question } from "../fixtures/unansweredQuestionChat"; +import { QuestionChat, question } from "../../fixtures/unansweredQuestionChat"; -const meta = { title: "Chat & Comments/Agent Chat Unanswered Questions", parameters: { layout: "fullscreen" }, component: QuestionChat, +const meta = { id: "chat-comments-agent-chat-unanswered-questions", + title: "Tasks and conversations/Questions and approvals/Chat & Comments/Agent Chat Unanswered Questions", parameters: { layout: "fullscreen" }, component: QuestionChat, beforeEach: () => { localStorage.removeItem(`paperclip:task-question-dismissals:user-board:${question.issueId}`); for (const key of Object.keys(localStorage)) { diff --git a/ui/storybook/stories/composer-question-flow.stories.tsx b/ui/storybook/stories/questions-and-approvals/composer-question-flow.stories.tsx similarity index 98% rename from ui/storybook/stories/composer-question-flow.stories.tsx rename to ui/storybook/stories/questions-and-approvals/composer-question-flow.stories.tsx index 4fd6af0f0e4..34ec7fba1e1 100644 --- a/ui/storybook/stories/composer-question-flow.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/composer-question-flow.stories.tsx @@ -85,7 +85,8 @@ function InteractiveComposerQuestions() { } const meta = { - title: "Composer/Question flow", + id: "composer-question-flow", + title: "Tasks and conversations/Questions and approvals/Composer/Question flow", component: InteractiveComposerQuestions, parameters: { layout: "padded", diff --git a/ui/storybook/stories/connection-reviews.stories.tsx b/ui/storybook/stories/questions-and-approvals/connection-reviews.stories.tsx similarity index 98% rename from ui/storybook/stories/connection-reviews.stories.tsx rename to ui/storybook/stories/questions-and-approvals/connection-reviews.stories.tsx index 360435d1f9b..825c61b0a8b 100644 --- a/ui/storybook/stories/connection-reviews.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/connection-reviews.stories.tsx @@ -17,7 +17,7 @@ import { toolsApi } from "@/api/tools"; import { issuesApi } from "@/api/issues"; import { ReviewQueueCard } from "@/pages/apps/ReviewQueueCard"; import type { ToolActionRequestListItem } from "@paperclipai/shared"; -import { storybookAgentMap } from "../fixtures/paperclipData"; +import { storybookAgentMap } from "../../fixtures/paperclipData"; const readRequest: RequestConfirmationInteraction = { ...pendingToolActionWriteInteraction, @@ -44,7 +44,8 @@ const readRequest: RequestConfirmationInteraction = { }, }; const meta = { - title: "Chat & Comments/Connection Reviews", + id: "chat-comments-connection-reviews", + title: "Tasks and conversations/Questions and approvals/Chat & Comments/Connection Reviews", parameters: { layout: "fullscreen" }, } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/issue-plan-decompositions.stories.tsx b/ui/storybook/stories/questions-and-approvals/issue-plan-decompositions.stories.tsx similarity index 94% rename from ui/storybook/stories/issue-plan-decompositions.stories.tsx rename to ui/storybook/stories/questions-and-approvals/issue-plan-decompositions.stories.tsx index e53f33c44f4..0e3ab134d01 100644 --- a/ui/storybook/stories/issue-plan-decompositions.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/issue-plan-decompositions.stories.tsx @@ -4,7 +4,7 @@ import { useQueryClient } from "@tanstack/react-query"; import type { AcceptedPlanDecompositionSummary } from "@paperclipai/shared"; import { IssuePlanDecompositionsSection } from "@/components/IssuePlanDecompositionsSection"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookAgentMap } from "../fixtures/paperclipData"; +import { storybookAgentMap } from "../../fixtures/paperclipData"; const issueId = "issue-plan-decomposition-story"; const issueIdentifier = "PAP-6831"; @@ -82,7 +82,8 @@ function HydratedSection({ } const meta = { - title: "Issue Detail/Plan Decompositions", + id: "issue-detail-plan-decompositions", + title: "Tasks and conversations/Questions and approvals/Issue Detail/Plan Decompositions", component: HydratedSection, args: { decompositions: [], diff --git a/ui/storybook/stories/issue-thread-interactions.stories.tsx b/ui/storybook/stories/questions-and-approvals/issue-thread-interactions.stories.tsx similarity index 99% rename from ui/storybook/stories/issue-thread-interactions.stories.tsx rename to ui/storybook/stories/questions-and-approvals/issue-thread-interactions.stories.tsx index 371082cbdab..d72c03559b7 100644 --- a/ui/storybook/stories/issue-thread-interactions.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/issue-thread-interactions.stories.tsx @@ -79,7 +79,7 @@ import type { RequestItemVerdictValue, SuggestTasksInteraction, } from "@/lib/issue-thread-interactions"; -import { storybookAgentMap } from "../fixtures/paperclipData"; +import { storybookAgentMap } from "../../fixtures/paperclipData"; const boardUserLabels = new Map([ [issueThreadInteractionFixtureMeta.currentUserId, "Riley Board"], @@ -432,7 +432,8 @@ function AutoOpenDeclineRequestConfirmationCard({ } const meta = { - title: "Chat & Comments/Issue Thread Interactions", + id: "chat-comments-issue-thread-interactions", + title: "Tasks and conversations/Questions and approvals/Chat & Comments/Issue Thread Interactions", parameters: { docs: { description: { diff --git a/ui/storybook/stories/rich-acp-interactions.stories.tsx b/ui/storybook/stories/questions-and-approvals/rich-acp-interactions.stories.tsx similarity index 98% rename from ui/storybook/stories/rich-acp-interactions.stories.tsx rename to ui/storybook/stories/questions-and-approvals/rich-acp-interactions.stories.tsx index 5f17004f7d5..929b9bbd2cb 100644 --- a/ui/storybook/stories/rich-acp-interactions.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/rich-acp-interactions.stories.tsx @@ -19,7 +19,7 @@ import type { AskUserQuestionsInteraction, RequestConfirmationInteraction, } from "@/lib/issue-thread-interactions"; -import { storybookAgentMap } from "../fixtures/paperclipData"; +import { storybookAgentMap } from "../../fixtures/paperclipData"; // Production components and production projection, with callbacks kept in memory. // No provider, runner, or control-plane mutation is made by these stories. @@ -181,7 +181,8 @@ function ProviderNotice() { } const meta = { - title: "Chat & Comments/Rich ACP interactions", + id: "chat-comments-rich-acp-interactions", + title: "Tasks and conversations/Questions and approvals/Chat & Comments/Rich ACP interactions", parameters: { layout: "fullscreen", docs: { description: { component: "Credential-free production UI fixtures. Callback receipts establish UI payload fidelity only; they do not claim provider execution or server persistence." } } }, } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/task-chat-interaction-above-composer.stories.tsx b/ui/storybook/stories/questions-and-approvals/task-chat-interaction-above-composer.stories.tsx similarity index 97% rename from ui/storybook/stories/task-chat-interaction-above-composer.stories.tsx rename to ui/storybook/stories/questions-and-approvals/task-chat-interaction-above-composer.stories.tsx index b8f896f8e44..2f35277bfc9 100644 --- a/ui/storybook/stories/task-chat-interaction-above-composer.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/task-chat-interaction-above-composer.stories.tsx @@ -19,8 +19,8 @@ import { pendingToolActionWriteInteraction, issueThreadInteractionFixtureMeta, } from "@/fixtures/issueThreadInteractionFixtures"; -import { composerAgentAppearance, composerAgents } from "../prototypes/composer-model-picker/fixtures"; -import { storybookAgentMap } from "../fixtures/paperclipData"; +import { composerAgentAppearance, composerAgents } from "../../prototypes/composer-model-picker/fixtures"; +import { storybookAgentMap } from "../../fixtures/paperclipData"; const agentMap = new Map(composerAgents.map((agent) => [agent.id, { id: agent.id, @@ -143,7 +143,8 @@ function InteractionAboveComposer({ interaction, mobile = false }: { interaction } const meta = { - title: "Composer/Interaction above composer", + id: "composer-interaction-above-composer", + title: "Tasks and conversations/Questions and approvals/Composer/Interaction above composer", component: InteractionAboveComposer, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/task-chat-unanswered-questions.stories.tsx b/ui/storybook/stories/questions-and-approvals/task-chat-unanswered-questions.stories.tsx similarity index 89% rename from ui/storybook/stories/task-chat-unanswered-questions.stories.tsx rename to ui/storybook/stories/questions-and-approvals/task-chat-unanswered-questions.stories.tsx index a5b698086f3..286a3090528 100644 --- a/ui/storybook/stories/task-chat-unanswered-questions.stories.tsx +++ b/ui/storybook/stories/questions-and-approvals/task-chat-unanswered-questions.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within, waitFor } from "storybook/test"; -import { QuestionChat } from "../fixtures/unansweredQuestionChat"; +import { QuestionChat } from "../../fixtures/unansweredQuestionChat"; const meta = { - title: "Chat & Comments/Task Chat Unanswered Questions", + id: "chat-comments-task-chat-unanswered-questions", + title: "Tasks and conversations/Questions and approvals/Chat & Comments/Task Chat Unanswered Questions", parameters: { layout: "fullscreen" }, component: QuestionChat, args: { conversationMode: false }, diff --git a/ui/storybook/stories/cloud-access-recovery.stories.tsx b/ui/storybook/stories/recovery/cloud-access-recovery.stories.tsx similarity index 85% rename from ui/storybook/stories/cloud-access-recovery.stories.tsx rename to ui/storybook/stories/recovery/cloud-access-recovery.stories.tsx index 4d01137a244..9db96e84bd6 100644 --- a/ui/storybook/stories/cloud-access-recovery.stories.tsx +++ b/ui/storybook/stories/recovery/cloud-access-recovery.stories.tsx @@ -2,7 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { CloudAccessError } from "@/components/CloudAccessGate"; const meta: Meta = { - title: "App/Connection recovery", + id: "app-connection-recovery", + title: "Projects and execution/Recovery/App/Connection recovery", component: CloudAccessError, parameters: { layout: "fullscreen" }, args: { temporary: true, retrying: false, onRetry: () => undefined }, diff --git a/ui/storybook/stories/execution-blocker-notice.stories.tsx b/ui/storybook/stories/recovery/execution-blocker-notice.stories.tsx similarity index 91% rename from ui/storybook/stories/execution-blocker-notice.stories.tsx rename to ui/storybook/stories/recovery/execution-blocker-notice.stories.tsx index e97cc80ada3..138e3cdb26c 100644 --- a/ui/storybook/stories/execution-blocker-notice.stories.tsx +++ b/ui/storybook/stories/recovery/execution-blocker-notice.stories.tsx @@ -8,7 +8,8 @@ client.setQueryData(queryKeys.issues.runs("storybook-recovery"), [ { runId: "stopped-run", agentId: "agent", status: "failed" }, ]); const meta = { - title: "Task chat/Recovery notice", + id: "task-chat-recovery-notice", + title: "Projects and execution/Recovery/Task chat/Recovery notice", component: ExecutionBlockerNotice, decorators: [Story =>
], args: { diff --git a/ui/storybook/stories/execution-recovery.stories.tsx b/ui/storybook/stories/recovery/execution-recovery.stories.tsx similarity index 98% rename from ui/storybook/stories/execution-recovery.stories.tsx rename to ui/storybook/stories/recovery/execution-recovery.stories.tsx index 609f5456c62..b057457b4bc 100644 --- a/ui/storybook/stories/execution-recovery.stories.tsx +++ b/ui/storybook/stories/recovery/execution-recovery.stories.tsx @@ -6,7 +6,7 @@ import { TaskChatRunnerTurn } from "@/components/task-chat/TaskChatRunnerTurn"; import { TaskChatLiveRunPill } from "@/components/task-chat/TaskChatLiveRunPill"; import { Textarea } from "@/components/ui/textarea"; import { queryKeys } from "@/lib/queryKeys"; -import { createIssue, storybookLiveRuns } from "../fixtures/paperclipData"; +import { createIssue, storybookLiveRuns } from "../../fixtures/paperclipData"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { ExecutionProjection } from "@paperclipai/shared"; import { expect, userEvent, within } from "storybook/test"; @@ -60,7 +60,8 @@ function QuietExecution({ execution }: { execution: ExecutionProjection }) { ); } const meta = { - title: "Tasks/Execution recovery", + id: "tasks-execution-recovery", + title: "Projects and execution/Recovery/Tasks/Execution recovery", component: QuietExecution, args: { execution: base }, decorators: [ diff --git a/ui/storybook/stories/issue-blocked-notice.stories.tsx b/ui/storybook/stories/recovery/issue-blocked-notice.stories.tsx similarity index 98% rename from ui/storybook/stories/issue-blocked-notice.stories.tsx rename to ui/storybook/stories/recovery/issue-blocked-notice.stories.tsx index a84bed3544a..e13cb2a9f64 100644 --- a/ui/storybook/stories/issue-blocked-notice.stories.tsx +++ b/ui/storybook/stories/recovery/issue-blocked-notice.stories.tsx @@ -33,7 +33,8 @@ function Frame({ label, children }: { label: string; children: React.ReactNode } } const meta = { - title: "Product/Issue/Blocked notice", + id: "product-issue-blocked-notice", + title: "Projects and execution/Recovery/Product/Issue/Blocked notice", component: IssueBlockedNotice, // Each story supplies its own props via `render`; this default satisfies the // component's one required prop (`blockers`) so the story args type-checks. diff --git a/ui/storybook/stories/model-capacity-retry.stories.tsx b/ui/storybook/stories/recovery/model-capacity-retry.stories.tsx similarity index 89% rename from ui/storybook/stories/model-capacity-retry.stories.tsx rename to ui/storybook/stories/recovery/model-capacity-retry.stories.tsx index 4ef94229a7d..4385292d116 100644 --- a/ui/storybook/stories/model-capacity-retry.stories.tsx +++ b/ui/storybook/stories/recovery/model-capacity-retry.stories.tsx @@ -2,7 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { IssueScheduledRetryCard } from "@/components/IssueScheduledRetryCard"; const meta = { - title: "Tasks/Model capacity retry", + id: "tasks-model-capacity-retry", + title: "Projects and execution/Recovery/Tasks/Model capacity retry", component: IssueScheduledRetryCard, decorators: [Story =>
], args: { diff --git a/ui/storybook/stories/recovery-notice-preview.stories.tsx b/ui/storybook/stories/recovery/recovery-notice-preview.stories.tsx similarity index 91% rename from ui/storybook/stories/recovery-notice-preview.stories.tsx rename to ui/storybook/stories/recovery/recovery-notice-preview.stories.tsx index a5f5c872484..5a4e4de344c 100644 --- a/ui/storybook/stories/recovery-notice-preview.stories.tsx +++ b/ui/storybook/stories/recovery/recovery-notice-preview.stories.tsx @@ -1,9 +1,10 @@ import { expect, userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { RecoveryNoticePreview } from "../prototypes/recovery-notice/RecoveryNoticePreview"; +import { RecoveryNoticePreview } from "../../prototypes/recovery-notice/RecoveryNoticePreview"; const meta = { - title: "Design previews/Recovery notice", + id: "design-previews-recovery-notice", + title: "Projects and execution/Recovery/Design previews/Recovery notice", component: RecoveryNoticePreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/scheduled-retry.stories.tsx b/ui/storybook/stories/recovery/scheduled-retry.stories.tsx similarity index 97% rename from ui/storybook/stories/scheduled-retry.stories.tsx rename to ui/storybook/stories/recovery/scheduled-retry.stories.tsx index a18e72c06d7..4a9acd8f5df 100644 --- a/ui/storybook/stories/scheduled-retry.stories.tsx +++ b/ui/storybook/stories/recovery/scheduled-retry.stories.tsx @@ -6,7 +6,7 @@ import { storybookExecutionWorkspaces, storybookIssueDocuments, storybookIssues, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const issueDocumentSummaries = storybookIssueDocuments.map(({ body: _body, ...summary }) => summary); @@ -144,7 +144,8 @@ function ScheduledRetrySurfaceStories() { } const meta = { - title: "Product/Issue Scheduled retry surfaces", + id: "product-issue-scheduled-retry-surfaces", + title: "Projects and execution/Recovery/Product/Issue Scheduled retry surfaces", component: ScheduledRetrySurfaceStories, parameters: { docs: { diff --git a/ui/storybook/stories/source-issue-recovery.stories.tsx b/ui/storybook/stories/recovery/source-issue-recovery.stories.tsx similarity index 99% rename from ui/storybook/stories/source-issue-recovery.stories.tsx rename to ui/storybook/stories/recovery/source-issue-recovery.stories.tsx index 6687556f838..fe49dfc09b9 100644 --- a/ui/storybook/stories/source-issue-recovery.stories.tsx +++ b/ui/storybook/stories/recovery/source-issue-recovery.stories.tsx @@ -5,7 +5,7 @@ import { Eye, ExternalLink, OctagonAlert, RefreshCw, TriangleAlert } from "lucid import { IssueRecoveryActionCard } from "@/components/IssueRecoveryActionCard"; import { IssueRow } from "@/components/IssueRow"; import { IssueBlockedNotice } from "@/components/IssueBlockedNotice"; -import { storybookAgentMap, storybookAgents, createIssue } from "../fixtures/paperclipData"; +import { storybookAgentMap, storybookAgents, createIssue } from "../../fixtures/paperclipData"; const claudeAgent = storybookAgents.find((agent) => agent.name.toLowerCase().startsWith("claude")) ?? storybookAgents[0]!; const codexAgent = storybookAgents.find((agent) => agent.name.toLowerCase().startsWith("codex")) ?? storybookAgents[0]!; @@ -574,7 +574,8 @@ function InboxRowPanel() { } const meta = { - title: "Paperclip/Source Issue Recovery", + id: "paperclip-source-issue-recovery", + title: "Projects and execution/Recovery/Paperclip/Source Issue Recovery", component: AllStatesPanel, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/source-resolved-fold.stories.tsx b/ui/storybook/stories/recovery/source-resolved-fold.stories.tsx similarity index 98% rename from ui/storybook/stories/source-resolved-fold.stories.tsx rename to ui/storybook/stories/recovery/source-resolved-fold.stories.tsx index ff39164c370..ba714b96e8e 100644 --- a/ui/storybook/stories/source-resolved-fold.stories.tsx +++ b/ui/storybook/stories/recovery/source-resolved-fold.stories.tsx @@ -54,7 +54,8 @@ function DefaultPanel() { } const meta = { - title: "Paperclip/Source-resolved Fold", + id: "paperclip-source-resolved-fold", + title: "Projects and execution/Recovery/Paperclip/Source-resolved Fold", component: DefaultPanel, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/workspace-branch-recovery/01-component.stories.tsx b/ui/storybook/stories/recovery/workspace-branch-recovery/01-component.stories.tsx similarity index 90% rename from ui/storybook/stories/workspace-branch-recovery/01-component.stories.tsx rename to ui/storybook/stories/recovery/workspace-branch-recovery/01-component.stories.tsx index 8e2fdd6d576..cdc9698d708 100644 --- a/ui/storybook/stories/workspace-branch-recovery/01-component.stories.tsx +++ b/ui/storybook/stories/recovery/workspace-branch-recovery/01-component.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; import { WorkspaceBaseRefRecoveryNotice } from "@/components/WorkspaceBaseRefRecoveryNotice"; -import { evidence, simulateRepair } from "./fixtures"; +import { evidence, simulateRepair } from "../../../support/workspace-branch-recovery/fixtures"; const meta = { - title: "Workspace branch recovery/01 Repair card", + id: "workspace-branch-recovery-01-repair-card", + title: "Projects and execution/Recovery/Workspace branch recovery/01 Repair card", component: WorkspaceBaseRefRecoveryNotice, args: { ...evidence, onRepair: simulateRepair }, parameters: { layout: "fullscreen", docs: { description: { component: "Proposed inline repair using the reusable component. Repository data and save/retry responses are simulated here; this story does not change a live task. The default-branch action appears only with a verified repository default." } } }, diff --git a/ui/storybook/stories/workspace-branch-recovery/02-task-journey.stories.tsx b/ui/storybook/stories/recovery/workspace-branch-recovery/02-task-journey.stories.tsx similarity index 92% rename from ui/storybook/stories/workspace-branch-recovery/02-task-journey.stories.tsx rename to ui/storybook/stories/recovery/workspace-branch-recovery/02-task-journey.stories.tsx index 48f827ca789..f30e7a75a23 100644 --- a/ui/storybook/stories/workspace-branch-recovery/02-task-journey.stories.tsx +++ b/ui/storybook/stories/recovery/workspace-branch-recovery/02-task-journey.stories.tsx @@ -8,7 +8,7 @@ import { WorkspaceBaseRefRecoveryNotice } from "@/components/WorkspaceBaseRefRec import { StatusBadge } from "@/components/StatusBadge"; import { PluginLauncherProvider } from "@/plugins/launchers"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; -import { evidence, simulateRepair } from "./fixtures"; +import { evidence, simulateRepair } from "../../../support/workspace-branch-recovery/fixtures"; function TaskConversation() { const [retried, setRetried] = useState(false); @@ -33,7 +33,8 @@ function Journey() { return }>} />; } const meta = { - title: "Workspace branch recovery/02 Task journey", component: Journey, + id: "workspace-branch-recovery-02-task-journey", + title: "Projects and execution/Recovery/Workspace branch recovery/02 Task journey", component: Journey, parameters: { layout: "fullscreen", initialEntries: ["/PAP/issues/PAP-24"], docs: { description: { component: "Interactive proposal in the production app shell, using the shared task bubbles, composer and repair component. Save/retry and branch lookup are simulated. Use master or choose release/next; a missing branch shows an inline error. No live task is modified." }, story: { inline: false } } }, } satisfies Meta; export default meta; diff --git a/ui/storybook/stories/routine-detail-c.stories.tsx b/ui/storybook/stories/routines/routine-detail-c.stories.tsx similarity index 98% rename from ui/storybook/stories/routine-detail-c.stories.tsx rename to ui/storybook/stories/routines/routine-detail-c.stories.tsx index d85a6924b66..f536ef04caf 100644 --- a/ui/storybook/stories/routine-detail-c.stories.tsx +++ b/ui/storybook/stories/routines/routine-detail-c.stories.tsx @@ -33,7 +33,7 @@ import { ActivitySection, } from "@/components/routine-sections/operate-sections"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookAgents, storybookProjects } from "../fixtures/paperclipData"; +import { storybookAgents, storybookProjects } from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const ROUTINE_ID = "routine-storybook"; @@ -440,7 +440,8 @@ function RoutineCShell({ } const meta: Meta = { - title: "Product/Routines · Detail (variation C)", + id: "product-routines-·-detail-variation-c", + title: "Automation and structured work/Routines, schedules, and triggers/Product/Routines · Detail (variation C)", component: RoutineCShell, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/routine-secrets.stories.tsx b/ui/storybook/stories/routines/routine-secrets.stories.tsx similarity index 97% rename from ui/storybook/stories/routine-secrets.stories.tsx rename to ui/storybook/stories/routines/routine-secrets.stories.tsx index 42bf771b2ba..8117b6ea4dc 100644 --- a/ui/storybook/stories/routine-secrets.stories.tsx +++ b/ui/storybook/stories/routines/routine-secrets.stories.tsx @@ -15,7 +15,7 @@ import { RoutineHistoryTab } from "@/components/RoutineHistoryTab"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { useCompany } from "@/context/CompanyContext"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookCompanies, storybookSecrets } from "../fixtures/paperclipData"; +import { storybookCompanies, storybookSecrets } from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; @@ -47,7 +47,8 @@ function StorybookRoutineFixtures({ } const meta: Meta = { - title: "Product/Routines · Secrets tab", + id: "product-routines-·-secrets-tab", + title: "Automation and structured work/Routines, schedules, and triggers/Product/Routines · Secrets tab", parameters: { layout: "fullscreen", a11y: { test: "off" }, diff --git a/ui/storybook/stories/routine-webhooks-ux.stories.tsx b/ui/storybook/stories/routines/routine-webhooks-ux.stories.tsx similarity index 98% rename from ui/storybook/stories/routine-webhooks-ux.stories.tsx rename to ui/storybook/stories/routines/routine-webhooks-ux.stories.tsx index a991e3598b7..093addf2e44 100644 --- a/ui/storybook/stories/routine-webhooks-ux.stories.tsx +++ b/ui/storybook/stories/routines/routine-webhooks-ux.stories.tsx @@ -1,6 +1,6 @@ -import { RoutineTriggerCard } from "../fixtures/routineTriggerCard"; -import { RoutineTriggerWizard, defaultTriggerDraft, type TriggerDraft } from "../fixtures/routineTriggerWizard"; -import { CopyField } from "../fixtures/routineWebhookFields"; +import { RoutineTriggerCard } from "../../fixtures/routineTriggerCard"; +import { RoutineTriggerWizard, defaultTriggerDraft, type TriggerDraft } from "../../fixtures/routineTriggerWizard"; +import { CopyField } from "../../fixtures/routineWebhookFields"; import { useCallback, useEffect, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; @@ -14,7 +14,7 @@ import { RoutineDetail } from "@/pages/RoutineDetail"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { Link, useNavigate, useParams } from "@/lib/router"; import { cn } from "@/lib/utils"; -import { WebhookReview } from "../fixtures/routineWebhooks"; +import { WebhookReview } from "../../fixtures/routineWebhooks"; // Deliberately Storybook-only: no production components or webhook APIs are changed. const endpoint = "https://acme.paperclip.example/api/routine-triggers/public/0123456789abcdef01234567/fire"; @@ -191,7 +191,8 @@ function Prototype(props: Props) { return } />; } const meta = { - title: "Product/Routines/Webhooks UX preview", + id: "product-routines-webhooks-ux-preview", + title: "Automation and structured work/Routines, schedules, and triggers/Product/Routines/Webhooks UX preview", component: Prototype, parameters: { layout: "fullscreen" }, args: { stage: "setup", sender: "custom", page: "triggers", triggerKind: "choose", wizardStep: 0, scheduleSaved: false, checkResult: "waiting" }, diff --git a/ui/storybook/stories/routine-webhooks.stories.tsx b/ui/storybook/stories/routines/routine-webhooks.stories.tsx similarity index 95% rename from ui/storybook/stories/routine-webhooks.stories.tsx rename to ui/storybook/stories/routines/routine-webhooks.stories.tsx index 3d6da63bf4f..5769c0258d2 100644 --- a/ui/storybook/stories/routine-webhooks.stories.tsx +++ b/ui/storybook/stories/routines/routine-webhooks.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { WebhookReview } from "../fixtures/routineWebhooks"; +import { WebhookReview } from "../../fixtures/routineWebhooks"; const meta = { - title: "Product/Routines/Webhooks", + id: "product-routines-webhooks", + title: "Automation and structured work/Routines, schedules, and triggers/Product/Routines/Webhooks", component: WebhookReview, parameters: { layout: "fullscreen" }, args: { signingMode: "bearer", state: "configured" }, diff --git a/ui/storybook/stories/routines-list-groups.stories.tsx b/ui/storybook/stories/routines/routines-list-groups.stories.tsx similarity index 96% rename from ui/storybook/stories/routines-list-groups.stories.tsx rename to ui/storybook/stories/routines/routines-list-groups.stories.tsx index cf4da482678..03955004ac3 100644 --- a/ui/storybook/stories/routines-list-groups.stories.tsx +++ b/ui/storybook/stories/routines/routines-list-groups.stories.tsx @@ -91,7 +91,8 @@ function GroupedList() { } const meta: Meta = { - title: "Product/Routines · List (grouped cards)", + id: "product-routines-·-list-grouped-cards", + title: "Automation and structured work/Routines, schedules, and triggers/Product/Routines · List (grouped cards)", component: GroupedList, parameters: { layout: "fullscreen" }, }; diff --git a/ui/storybook/stories/agent-connection-reuse.stories.tsx b/ui/storybook/stories/runs-adapters/agent-connection-reuse.stories.tsx similarity index 95% rename from ui/storybook/stories/agent-connection-reuse.stories.tsx rename to ui/storybook/stories/runs-adapters/agent-connection-reuse.stories.tsx index d3f98a949d0..2fd484a1c5c 100644 --- a/ui/storybook/stories/agent-connection-reuse.stories.tsx +++ b/ui/storybook/stories/runs-adapters/agent-connection-reuse.stories.tsx @@ -9,7 +9,7 @@ import { resetOnboardingFixtureState, setOnboardingFixtureState, STORYBOOK_SANDBOX_ENVIRONMENT_ID, -} from "../fixtures/onboardingEnvironment"; +} from "../../fixtures/onboardingEnvironment"; function ConnectionStory({ adapterType = "claude_local", @@ -48,7 +48,8 @@ function ConnectionStory({ ); } const meta = { - title: "Onboarding/Saved connections", + id: "onboarding-saved-connections", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/Onboarding/Saved connections", component: ConnectionStory, parameters: { layout: "centered", diff --git a/ui/storybook/stories/ai-connection-usage.stories.tsx b/ui/storybook/stories/runs-adapters/ai-connection-usage.stories.tsx similarity index 93% rename from ui/storybook/stories/ai-connection-usage.stories.tsx rename to ui/storybook/stories/runs-adapters/ai-connection-usage.stories.tsx index d3b47a3a386..849cc67ac65 100644 --- a/ui/storybook/stories/ai-connection-usage.stories.tsx +++ b/ui/storybook/stories/runs-adapters/ai-connection-usage.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, waitFor, within } from "storybook/test"; -import { AiConnectionUsageReview, USAGE_REVIEW_SCENARIOS } from "../prototypes/AiConnectionUsageReview"; +import { AiConnectionUsageReview, USAGE_REVIEW_SCENARIOS } from "../../prototypes/AiConnectionUsageReview"; const { codex, claude, grok, openRouter, unavailable } = USAGE_REVIEW_SCENARIOS; const meta = { - title: "AI Connections/Usage limits", + id: "ai-connections-usage-limits", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Usage limits", component: AiConnectionUsageReview, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/ai-connections.stories.tsx b/ui/storybook/stories/runs-adapters/ai-connections.stories.tsx similarity index 97% rename from ui/storybook/stories/ai-connections.stories.tsx rename to ui/storybook/stories/runs-adapters/ai-connections.stories.tsx index 10f3419dcee..100856e459b 100644 --- a/ui/storybook/stories/ai-connections.stories.tsx +++ b/ui/storybook/stories/runs-adapters/ai-connections.stories.tsx @@ -1,17 +1,18 @@ -import { AiReviewFrame, aiReviewContext } from "../prototypes/AiReviewFrame"; -import { AiTaskConnectionReview } from "../prototypes/AiTaskConnectionReview"; +import { AiReviewFrame, aiReviewContext } from "../../prototypes/AiReviewFrame"; +import { AiTaskConnectionReview } from "../../prototypes/AiTaskConnectionReview"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { AiConnectionsReview } from "../prototypes/AiConnectionsReview"; -import { AiConnectorPages } from "../prototypes/AiConnectorPages"; +import { AiConnectionsReview } from "../../prototypes/AiConnectionsReview"; +import { AiConnectorPages } from "../../prototypes/AiConnectorPages"; import { AI_REVIEW_BINDING, AI_REVIEW_CONNECTIONS, AI_REVIEW_REQUIREMENT, -} from "../fixtures/aiConnections"; +} from "../../fixtures/aiConnections"; const meta = { - title: "AI Connections/Review", + id: "ai-connections-review", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Review", component: AiConnectionsReview, parameters: { layout: "fullscreen" }, decorators: [(Story, context) => ( diff --git a/ui/storybook/stories/connection-pool-connector.stories.tsx b/ui/storybook/stories/runs-adapters/connection-pool-connector.stories.tsx similarity index 85% rename from ui/storybook/stories/connection-pool-connector.stories.tsx rename to ui/storybook/stories/runs-adapters/connection-pool-connector.stories.tsx index d6dac52e8e9..dac2a11391b 100644 --- a/ui/storybook/stories/connection-pool-connector.stories.tsx +++ b/ui/storybook/stories/runs-adapters/connection-pool-connector.stories.tsx @@ -1,7 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { ConnectionPoolConnectorHost } from "../prototypes/ConnectionPoolConnectorHost"; -const meta = { title: "Connectors/Pool host", component: ConnectionPoolConnectorHost, parameters: { layout: "fullscreen" } } satisfies Meta; +import { ConnectionPoolConnectorHost } from "../../prototypes/ConnectionPoolConnectorHost"; +const meta = { id: "connectors-pool-host", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/Connectors/Pool host", component: ConnectionPoolConnectorHost, parameters: { layout: "fullscreen" } } satisfies Meta; export default meta; type Story = StoryObj; export const Catalog: Story = {}; diff --git a/ui/storybook/stories/connection-pools.stories.tsx b/ui/storybook/stories/runs-adapters/connection-pools.stories.tsx similarity index 92% rename from ui/storybook/stories/connection-pools.stories.tsx rename to ui/storybook/stories/runs-adapters/connection-pools.stories.tsx index 0f480d7156c..54ee9f59d36 100644 --- a/ui/storybook/stories/connection-pools.stories.tsx +++ b/ui/storybook/stories/runs-adapters/connection-pools.stories.tsx @@ -10,7 +10,7 @@ import { ComposerRunSettingsPicker } from "@/components/task-chat/ComposerRunSet import type { ComposerRunSettings } from "@/components/task-chat/composer-run-settings"; import { RunRetryDetails } from "@/components/RunRetryDetails"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookAgents, storybookIssues } from "../fixtures/paperclipData"; +import { storybookAgents, storybookIssues } from "../../fixtures/paperclipData"; const companyId = "company-storybook", agentId = "agent-codex"; const poolId = "10000000-0000-4000-8000-000000000001"; @@ -74,7 +74,8 @@ function PoolSurfaces({ scenario = "overview" }: { scenario?: Scenario }) { {(overview || ["run-codex", "run-claude", "fallback", "retry"].includes(scenario)) &&

Run account and runtime

} ; } -const meta = { title: "AI Connections/Connection pools", component: PoolSurfaces, parameters: { layout: "fullscreen", docs: { description: { component: "All user-facing Core additions for the private connection-pool plugin. These stories mount the production selector, composer, quota banners, and run details/retry metadata. Routing requires manual operator configuration; there is no settings toggle. Accounts are fixtures; no live provider calls or instance mutations occur." } } }, render: args => } satisfies Meta; +const meta = { id: "ai-connections-connection-pools", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Connection pools", component: PoolSurfaces, parameters: { layout: "fullscreen", docs: { description: { component: "All user-facing Core additions for the private connection-pool plugin. These stories mount the production selector, composer, quota banners, and run details/retry metadata. Routing requires manual operator configuration; there is no settings toggle. Accounts are fixtures; no live provider calls or instance mutations occur." } } }, render: args => } satisfies Meta; export default meta; type Story = StoryObj; export const AllCoreSurfaces: Story = { args: { scenario: "overview" } }; diff --git a/ui/storybook/stories/dot-runner-connection.stories.tsx b/ui/storybook/stories/runs-adapters/dot-runner-connection.stories.tsx similarity index 94% rename from ui/storybook/stories/dot-runner-connection.stories.tsx rename to ui/storybook/stories/runs-adapters/dot-runner-connection.stories.tsx index fd00dd211b6..18b0cbb3ac5 100644 --- a/ui/storybook/stories/dot-runner-connection.stories.tsx +++ b/ui/storybook/stories/runs-adapters/dot-runner-connection.stories.tsx @@ -33,7 +33,8 @@ function ConnectionForm() {
; } const meta = { - title: "Assistant connections/Dot Runner", component: ConnectionForm, + id: "assistant-connections-dot-runner", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/Assistant connections/Dot Runner", component: ConnectionForm, parameters: { layout: "padded", docs: { description: { component: "Production Dot pairing component. API responses, pairing code and account state are synthetic. This preview does not qualify a real Dot account." } } }, beforeEach: ({ parameters }) => installFixture(Boolean(parameters.connected)), } satisfies Meta; diff --git a/ui/storybook/stories/local-ai-subscription.stories.tsx b/ui/storybook/stories/runs-adapters/local-ai-subscription.stories.tsx similarity index 92% rename from ui/storybook/stories/local-ai-subscription.stories.tsx rename to ui/storybook/stories/runs-adapters/local-ai-subscription.stories.tsx index b5c8ddb8dda..57a7cf82cc9 100644 --- a/ui/storybook/stories/local-ai-subscription.stories.tsx +++ b/ui/storybook/stories/runs-adapters/local-ai-subscription.stories.tsx @@ -3,7 +3,8 @@ import { expect, userEvent, waitFor, within } from "storybook/test"; import { LocalProviderLoginInstructions } from "@/components/AdapterLoginChrome"; const meta = { - title: "AI Connections/Provider routing/07 Subscription sign-in", + id: "ai-connections-provider-routing-07-subscription-sign-in", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/07 Subscription sign-in", component: LocalProviderLoginInstructions, parameters: { layout: "centered" }, } satisfies Meta; diff --git a/ui/storybook/stories/provider-routing-00-overview.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-00-overview.stories.tsx similarity index 52% rename from ui/storybook/stories/provider-routing-00-overview.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-00-overview.stories.tsx index 868505913ab..32cbb80c225 100644 --- a/ui/storybook/stories/provider-routing-00-overview.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-00-overview.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { ReviewIndex } from "../prototypes/provider-routing/ReviewIndex"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; -import { reviewLifecycle } from "../prototypes/provider-routing/story-support"; +import { ReviewIndex } from "../../prototypes/provider-routing/ReviewIndex"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; +import { reviewLifecycle } from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/00 Overview", + id: "ai-connections-provider-routing-00-overview", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/00 Overview", component: ReviewIndex, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-01-onboarding.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-01-onboarding.stories.tsx similarity index 89% rename from ui/storybook/stories/provider-routing-01-onboarding.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-01-onboarding.stories.tsx index 1fa29f8a343..ee0cf983ab7 100644 --- a/ui/storybook/stories/provider-routing-01-onboarding.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-01-onboarding.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { Onboarding } from "../prototypes/provider-routing/AgentSetup"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; -import { reviewLifecycle } from "../prototypes/provider-routing/story-support"; +import { Onboarding } from "../../prototypes/provider-routing/AgentSetup"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; +import { reviewLifecycle } from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/01 Onboarding", + id: "ai-connections-provider-routing-01-onboarding", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/01 Onboarding", component: Onboarding, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-02-connect.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-02-connect.stories.tsx similarity index 88% rename from ui/storybook/stories/provider-routing-02-connect.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-02-connect.stories.tsx index 8d0f42ca24c..04294737078 100644 --- a/ui/storybook/stories/provider-routing-02-connect.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-02-connect.stories.tsx @@ -1,14 +1,15 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { ProviderCatalogReview } from "../prototypes/provider-routing/ConnectionCatalog"; -import { ConnectionSetup } from "../prototypes/provider-routing/ConnectionSetup"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; +import { ProviderCatalogReview } from "../../prototypes/provider-routing/ConnectionCatalog"; +import { ConnectionSetup } from "../../prototypes/provider-routing/ConnectionSetup"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; import { choose, reviewLifecycle, -} from "../prototypes/provider-routing/story-support"; +} from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/02 Connect", + id: "ai-connections-provider-routing-02-connect", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/02 Connect", component: ConnectionSetup, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-03-agent.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-03-agent.stories.tsx similarity index 96% rename from ui/storybook/stories/provider-routing-03-agent.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-03-agent.stories.tsx index 2b45c5ddfd8..dd7b66339d1 100644 --- a/ui/storybook/stories/provider-routing-03-agent.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-03-agent.stories.tsx @@ -1,13 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, waitFor, within } from "storybook/test"; -import { AgentSetup } from "../prototypes/provider-routing/AgentSetup"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; +import { AgentSetup } from "../../prototypes/provider-routing/AgentSetup"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; import { choose, reviewLifecycle, -} from "../prototypes/provider-routing/story-support"; +} from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/03 Agent", + id: "ai-connections-provider-routing-03-agent", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/03 Agent", component: AgentSetup, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-04-manage.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-04-manage.stories.tsx similarity index 89% rename from ui/storybook/stories/provider-routing-04-manage.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-04-manage.stories.tsx index 7e5299a7456..3b40f1b84ea 100644 --- a/ui/storybook/stories/provider-routing-04-manage.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-04-manage.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { Management } from "../prototypes/provider-routing/Management"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; -import { reviewLifecycle } from "../prototypes/provider-routing/story-support"; +import { Management } from "../../prototypes/provider-routing/Management"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; +import { reviewLifecycle } from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/04 Manage", + id: "ai-connections-provider-routing-04-manage", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/04 Manage", component: Management, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-05-recovery.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-05-recovery.stories.tsx similarity index 87% rename from ui/storybook/stories/provider-routing-05-recovery.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-05-recovery.stories.tsx index 222c79ec859..e1e9b33a8f1 100644 --- a/ui/storybook/stories/provider-routing-05-recovery.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-05-recovery.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { Recovery } from "../prototypes/provider-routing/Recovery"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; -import { reviewLifecycle } from "../prototypes/provider-routing/story-support"; +import { Recovery } from "../../prototypes/provider-routing/Recovery"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; +import { reviewLifecycle } from "../../prototypes/provider-routing/story-support"; const meta = { - title: "AI Connections/Provider routing/05 Recovery", + id: "ai-connections-provider-routing-05-recovery", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/05 Recovery", component: Recovery, parameters: { layout: "fullscreen" }, ...reviewLifecycle, diff --git a/ui/storybook/stories/provider-routing-06-production.stories.tsx b/ui/storybook/stories/runs-adapters/provider-routing-06-production.stories.tsx similarity index 97% rename from ui/storybook/stories/provider-routing-06-production.stories.tsx rename to ui/storybook/stories/runs-adapters/provider-routing-06-production.stories.tsx index 774e3481073..2454daf4179 100644 --- a/ui/storybook/stories/provider-routing-06-production.stories.tsx +++ b/ui/storybook/stories/runs-adapters/provider-routing-06-production.stories.tsx @@ -15,8 +15,8 @@ import type { AiManagedConnectionSummary, } from "@paperclipai/shared"; import { expect, userEvent, waitFor, within } from "storybook/test"; -import { ProviderCatalogReview } from "../prototypes/provider-routing/ConnectionCatalog"; -import { ReviewFrame } from "../prototypes/provider-routing/shared"; +import { ProviderCatalogReview } from "../../prototypes/provider-routing/ConnectionCatalog"; +import { ReviewFrame } from "../../prototypes/provider-routing/shared"; const companyId = "00000000-0000-4000-8000-000000000001"; const accounts: AiManagedConnectionSummary[] = [ @@ -161,7 +161,8 @@ function AgentConnectionModes() { ; } const meta = { - title: "AI Connections/Provider routing/06 Production components", + id: "ai-connections-provider-routing-06-production-components", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/AI Connections/Provider routing/06 Production components", parameters: { layout: "fullscreen" }, decorators: [ (Story) => ( diff --git a/ui/storybook/stories/session-goals.stories.tsx b/ui/storybook/stories/runs-adapters/session-goals.stories.tsx similarity index 97% rename from ui/storybook/stories/session-goals.stories.tsx rename to ui/storybook/stories/runs-adapters/session-goals.stories.tsx index b9634405186..6658a1ae754 100644 --- a/ui/storybook/stories/session-goals.stories.tsx +++ b/ui/storybook/stories/runs-adapters/session-goals.stories.tsx @@ -159,7 +159,8 @@ function SessionGoalStates() { } const meta = { - title: "Composer/Session goals", + id: "composer-session-goals", + title: "Agents and reusable capabilities/Runs, harnesses, and model accounts/Composer/Session goals", component: SessionGoalStates, parameters: { docs: { diff --git a/ui/storybook/stories/credential-access/01-notice.stories.tsx b/ui/storybook/stories/secrets/credential-access/01-notice.stories.tsx similarity index 91% rename from ui/storybook/stories/credential-access/01-notice.stories.tsx rename to ui/storybook/stories/secrets/credential-access/01-notice.stories.tsx index 91ea2eb8a6d..3e541190fae 100644 --- a/ui/storybook/stories/credential-access/01-notice.stories.tsx +++ b/ui/storybook/stories/secrets/credential-access/01-notice.stories.tsx @@ -2,7 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { TaskChatMarker } from "@/components/task-chat/TaskChatMarker"; const meta = { - title: "Task credential access/01 Notice", + id: "task-credential-access-01-notice", + title: "Apps, channels, and extensions/Secrets and proposals/Task credential access/01 Notice", component: TaskChatMarker, parameters: { layout: "centered" }, decorators: [Story =>
], diff --git a/ui/storybook/stories/credential-access/02-task-page.stories.tsx b/ui/storybook/stories/secrets/credential-access/02-task-page.stories.tsx similarity index 82% rename from ui/storybook/stories/credential-access/02-task-page.stories.tsx rename to ui/storybook/stories/secrets/credential-access/02-task-page.stories.tsx index e60aa8f9773..5914d100ef0 100644 --- a/ui/storybook/stories/credential-access/02-task-page.stories.tsx +++ b/ui/storybook/stories/secrets/credential-access/02-task-page.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { CredentialTaskPage } from "./CredentialTaskPage"; +import { CredentialTaskPage } from "../../../support/credential-access/CredentialTaskPage"; const meta = { - title: "Task credential access/02 Task page", + id: "task-credential-access-02-task-page", + title: "Apps, channels, and extensions/Secrets and proposals/Task credential access/02 Task page", component: CredentialTaskPage, parameters: { layout: "fullscreen", waitForViewport: true, docs: { description: { component: "Production task detail and app shell. The latest credential failure stays expanded in the conversation and names the selected credential. The settings link uses the existing agent runtime route. Example data is local to Storybook." } }, diff --git a/ui/storybook/stories/environment-variables-editor.stories.tsx b/ui/storybook/stories/secrets/environment-variables-editor.stories.tsx similarity index 98% rename from ui/storybook/stories/environment-variables-editor.stories.tsx rename to ui/storybook/stories/secrets/environment-variables-editor.stories.tsx index b84afbd1801..e0aaa83dc11 100644 --- a/ui/storybook/stories/environment-variables-editor.stories.tsx +++ b/ui/storybook/stories/secrets/environment-variables-editor.stories.tsx @@ -91,7 +91,8 @@ function Editor({ } const meta: Meta = { - title: "Product/Environment Variables Editor", + id: "product-environment-variables-editor", + title: "Apps, channels, and extensions/Secrets and proposals/Product/Environment Variables Editor", parameters: { layout: "fullscreen", a11y: { test: "off" } }, }; export default meta; diff --git a/ui/storybook/stories/secrets.stories.tsx b/ui/storybook/stories/secrets/secrets.stories.tsx similarity index 98% rename from ui/storybook/stories/secrets.stories.tsx rename to ui/storybook/stories/secrets/secrets.stories.tsx index 27fb6592397..fb039058f1f 100644 --- a/ui/storybook/stories/secrets.stories.tsx +++ b/ui/storybook/stories/secrets/secrets.stories.tsx @@ -15,7 +15,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Badge } from "@/components/ui/badge"; import { useCompany } from "@/context/CompanyContext"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookCompanies, storybookSecrets } from "../fixtures/paperclipData"; +import { storybookCompanies, storybookSecrets } from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; @@ -118,7 +118,8 @@ function StorybookSecretsFixtures({ children }: { children: ReactNode }) { } const meta: Meta = { - title: "Product/Secrets", + id: "product-secrets", + title: "Apps, channels, and extensions/Secrets and proposals/Product/Secrets", parameters: { layout: "fullscreen", a11y: { diff --git a/ui/storybook/stories/user-secrets.stories.tsx b/ui/storybook/stories/secrets/user-secrets.stories.tsx similarity index 98% rename from ui/storybook/stories/user-secrets.stories.tsx rename to ui/storybook/stories/secrets/user-secrets.stories.tsx index 2244ba4a20e..43544b079e0 100644 --- a/ui/storybook/stories/user-secrets.stories.tsx +++ b/ui/storybook/stories/secrets/user-secrets.stories.tsx @@ -140,7 +140,8 @@ function Section({ title, children }: { title: string; children: ReactNode }) { } const meta: Meta = { - title: "Product/User secrets", + id: "product-user-secrets", + title: "Apps, channels, and extensions/Secrets and proposals/Product/User secrets", parameters: { layout: "fullscreen", a11y: { test: "off" } }, }; export default meta; diff --git a/ui/storybook/stories/agent-skill-row.stories.tsx b/ui/storybook/stories/skills/agent-skill-row.stories.tsx similarity index 95% rename from ui/storybook/stories/agent-skill-row.stories.tsx rename to ui/storybook/stories/skills/agent-skill-row.stories.tsx index fef898a117d..c90f4e411f2 100644 --- a/ui/storybook/stories/agent-skill-row.stories.tsx +++ b/ui/storybook/stories/skills/agent-skill-row.stories.tsx @@ -42,7 +42,8 @@ const READONLY: AgentSkillRowData = { }; const meta: Meta = { - title: "Agent Skills/AgentSkillRow", + id: "agent-skills-agentskillrow", + title: "Agents and reusable capabilities/Skills and Skill Studio/Agent Skills/AgentSkillRow", component: AgentSkillRow, parameters: { layout: "padded" }, decorators: [ diff --git a/ui/storybook/stories/agent-skills-edge.stories.tsx b/ui/storybook/stories/skills/agent-skills-edge.stories.tsx similarity index 98% rename from ui/storybook/stories/agent-skills-edge.stories.tsx rename to ui/storybook/stories/skills/agent-skills-edge.stories.tsx index ff04589393c..939846b4fdd 100644 --- a/ui/storybook/stories/agent-skills-edge.stories.tsx +++ b/ui/storybook/stories/skills/agent-skills-edge.stories.tsx @@ -192,7 +192,8 @@ function EdgeState({ agent, snapshot }: { agent: Agent; snapshot: AgentSkillSnap } const meta: Meta = { - title: "QA / Agent skills edge states", + id: "qa-agent-skills-edge-states", + title: "Agents and reusable capabilities/Skills and Skill Studio/QA / Agent skills edge states", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/agents-using-skill.stories.tsx b/ui/storybook/stories/skills/agents-using-skill.stories.tsx similarity index 97% rename from ui/storybook/stories/agents-using-skill.stories.tsx rename to ui/storybook/stories/skills/agents-using-skill.stories.tsx index f024c2283af..d813286384f 100644 --- a/ui/storybook/stories/agents-using-skill.stories.tsx +++ b/ui/storybook/stories/skills/agents-using-skill.stories.tsx @@ -119,7 +119,8 @@ function Seed({ skill, children }: { skill: CompanySkillDetail; children: ReactN } const meta: Meta = { - title: "Skill Studio/AgentsUsingSkill", + id: "skill-studio-agentsusingskill", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skill Studio/AgentsUsingSkill", parameters: { layout: "centered" }, }; export default meta; diff --git a/ui/storybook/stories/fork-skill-flow.stories.tsx b/ui/storybook/stories/skills/fork-skill-flow.stories.tsx similarity index 98% rename from ui/storybook/stories/fork-skill-flow.stories.tsx rename to ui/storybook/stories/skills/fork-skill-flow.stories.tsx index 1c250e0bd8c..c635419c204 100644 --- a/ui/storybook/stories/fork-skill-flow.stories.tsx +++ b/ui/storybook/stories/skills/fork-skill-flow.stories.tsx @@ -152,7 +152,8 @@ function Seed({ } const meta: Meta = { - title: "Skill Studio/EditACopy", + id: "skill-studio-editacopy", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skill Studio/EditACopy", parameters: { layout: "centered" }, }; export default meta; diff --git a/ui/storybook/stories/frontmatter-panel.stories.tsx b/ui/storybook/stories/skills/frontmatter-panel.stories.tsx similarity index 97% rename from ui/storybook/stories/frontmatter-panel.stories.tsx rename to ui/storybook/stories/skills/frontmatter-panel.stories.tsx index f4f510bc26d..a09ae7bb3eb 100644 --- a/ui/storybook/stories/frontmatter-panel.stories.tsx +++ b/ui/storybook/stories/skills/frontmatter-panel.stories.tsx @@ -66,7 +66,8 @@ metadata: version: 2`; const meta: Meta = { - title: "Product/Skill Studio · FrontmatterPanel", + id: "product-skill-studio-·-frontmatterpanel", + title: "Agents and reusable capabilities/Skills and Skill Studio/Product/Skill Studio · FrontmatterPanel", component: Harness, parameters: { layout: "padded" }, }; diff --git a/ui/storybook/stories/github-skills-journey.stories.tsx b/ui/storybook/stories/skills/github-skills-journey.stories.tsx similarity index 97% rename from ui/storybook/stories/github-skills-journey.stories.tsx rename to ui/storybook/stories/skills/github-skills-journey.stories.tsx index fba52de7597..aad4fb527d5 100644 --- a/ui/storybook/stories/github-skills-journey.stories.tsx +++ b/ui/storybook/stories/skills/github-skills-journey.stories.tsx @@ -11,7 +11,7 @@ import { SkillStudio } from "@/pages/SkillStudio"; import { PluginLauncherProvider } from "@/plugins/launchers"; import { Link, Route, Routes, useNavigate } from "@/lib/router"; import { queryKeys } from "@/lib/queryKeys"; -import { COMPANY_ID, COMMIT, installFixtures, repositoryCandidates, repositoryFixture, SOURCE_ID } from "../fixtures/githubSkillSources"; +import { COMPANY_ID, COMMIT, installFixtures, repositoryCandidates, repositoryFixture, SOURCE_ID } from "../../fixtures/githubSkillSources"; type Step = "start" | "repository" | "selection" | "imported" | "library" | "detail" | "agents" | "assigned" | "refresh" | "new-skills" | "scanning" | "saving"; const routes: Record = { @@ -89,7 +89,8 @@ export function GitHubSkillsJourney({ step = "start", packageScenario }: { step? } const meta = { - title: "Skills/GitHub sync journey", + id: "skills-github-sync-journey", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills/GitHub sync journey", component: GitHubSkillsJourney, tags: ["!autodocs"], parameters: { diff --git a/ui/storybook/stories/github-synced-skills.stories.tsx b/ui/storybook/stories/skills/github-synced-skills.stories.tsx similarity index 97% rename from ui/storybook/stories/github-synced-skills.stories.tsx rename to ui/storybook/stories/skills/github-synced-skills.stories.tsx index f73dcd99fa1..5fbf0aa45ad 100644 --- a/ui/storybook/stories/github-synced-skills.stories.tsx +++ b/ui/storybook/stories/skills/github-synced-skills.stories.tsx @@ -1,7 +1,7 @@ import { userEvent, within } from "storybook/test"; import { useEffect, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { COMPANY_ID, SOURCE_ID, installFixtures, type RepositoryScenario } from "../fixtures/githubSkillSources"; +import { COMPANY_ID, SOURCE_ID, installFixtures, type RepositoryScenario } from "../../fixtures/githubSkillSources"; import { Link, Route, Routes, useNavigate } from "@/lib/router"; import { SkillSources } from "@/pages/SkillSources"; @@ -33,7 +33,8 @@ function SourcesStory({ view = "sources", empty = false, needsConnection = false } const meta = { - title: "Skills/GitHub synced skills", + id: "skills-github-synced-skills", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills/GitHub synced skills", component: SourcesStory, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/repository-skill-packages/repository-skill-packages.stories.tsx b/ui/storybook/stories/skills/repository-skill-packages/repository-skill-packages.stories.tsx similarity index 93% rename from ui/storybook/stories/repository-skill-packages/repository-skill-packages.stories.tsx rename to ui/storybook/stories/skills/repository-skill-packages/repository-skill-packages.stories.tsx index 3e8cb341a14..baf30f5e971 100644 --- a/ui/storybook/stories/repository-skill-packages/repository-skill-packages.stories.tsx +++ b/ui/storybook/stories/skills/repository-skill-packages/repository-skill-packages.stories.tsx @@ -3,7 +3,8 @@ import { userEvent, within } from 'storybook/test'; import { GitHubSkillsJourney } from '../github-skills-journey.stories'; const meta = { - title: 'Skills/Repository packages', component: GitHubSkillsJourney, + id: "skills-repository-packages", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills/Repository packages", component: GitHubSkillsJourney, tags: ['!autodocs'], parameters: { layout: 'fullscreen', docs: { description: { component: 'Production Skills UI in the app shell with repository fixtures. Repository files stay together. Select which skills appear in your library. No GitHub access or runtime execution occurs in this preview.' } } }, args: { packageScenario: 'ready' }, diff --git a/ui/storybook/stories/skill-import-progress.stories.tsx b/ui/storybook/stories/skills/skill-import-progress.stories.tsx similarity index 90% rename from ui/storybook/stories/skill-import-progress.stories.tsx rename to ui/storybook/stories/skills/skill-import-progress.stories.tsx index fc1d8dda02b..77a177d657d 100644 --- a/ui/storybook/stories/skill-import-progress.stories.tsx +++ b/ui/storybook/stories/skills/skill-import-progress.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { SkillImportProgress } from '@/pages/skills/SkillImportProgress'; -import { candidates } from '../fixtures/githubSkillSources'; +import { candidates } from '../../fixtures/githubSkillSources'; const meta = { - title: 'Skills/Import progress', + id: "skills-import-progress", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills/Import progress", component: SkillImportProgress, args: { repository: 'acme/team-skills' }, decorators: [Story =>
], diff --git a/ui/storybook/stories/skill-policy-surfaces.stories.tsx b/ui/storybook/stories/skills/skill-policy-surfaces.stories.tsx similarity index 94% rename from ui/storybook/stories/skill-policy-surfaces.stories.tsx rename to ui/storybook/stories/skills/skill-policy-surfaces.stories.tsx index 1b3356d2c1d..11b17e2daee 100644 --- a/ui/storybook/stories/skill-policy-surfaces.stories.tsx +++ b/ui/storybook/stories/skills/skill-policy-surfaces.stories.tsx @@ -34,7 +34,8 @@ function Frame({ label, width, children }: { label: string; width: number; child } const meta = { - title: "Product/Skills/Policy surfaces", + id: "product-skills-policy-surfaces", + title: "Agents and reusable capabilities/Skills and Skill Studio/Product/Skills/Policy surfaces", component: SkillPolicyDenialNotice, args: { denial: policyDenial() }, parameters: { diff --git a/ui/storybook/stories/skills-store-detail.stories.tsx b/ui/storybook/stories/skills/skills-store-detail.stories.tsx similarity index 98% rename from ui/storybook/stories/skills-store-detail.stories.tsx rename to ui/storybook/stories/skills/skills-store-detail.stories.tsx index 7d91ba56b09..1661541bb91 100644 --- a/ui/storybook/stories/skills-store-detail.stories.tsx +++ b/ui/storybook/stories/skills/skills-store-detail.stories.tsx @@ -200,7 +200,8 @@ function SkillDetailHarness({ initialTab = "overview" as DetailTab }: { initialT } const meta: Meta = { - title: "Skills Store/Skill detail", + id: "skills-store-skill-detail", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills Store/Skill detail", component: SkillDetailHarness, parameters: { layout: "fullscreen" }, }; diff --git a/ui/storybook/stories/skills-store-discovery.stories.tsx b/ui/storybook/stories/skills/skills-store-discovery.stories.tsx similarity index 98% rename from ui/storybook/stories/skills-store-discovery.stories.tsx rename to ui/storybook/stories/skills/skills-store-discovery.stories.tsx index 2bcb7c2c988..3ffb36ab6db 100644 --- a/ui/storybook/stories/skills-store-discovery.stories.tsx +++ b/ui/storybook/stories/skills/skills-store-discovery.stories.tsx @@ -306,7 +306,8 @@ function DiscoveryGridHarness({ } const meta: Meta = { - title: "Skills Store/Discovery grid", + id: "skills-store-discovery-grid", + title: "Agents and reusable capabilities/Skills and Skill Studio/Skills Store/Discovery grid", component: DiscoveryGridHarness, parameters: { layout: "fullscreen" }, }; diff --git a/ui/storybook/stories/composer-queued-messages.stories.tsx b/ui/storybook/stories/steering/composer-queued-messages.stories.tsx similarity index 96% rename from ui/storybook/stories/composer-queued-messages.stories.tsx rename to ui/storybook/stories/steering/composer-queued-messages.stories.tsx index b2dfe5a561b..d23c3ba4c9c 100644 --- a/ui/storybook/stories/composer-queued-messages.stories.tsx +++ b/ui/storybook/stories/steering/composer-queued-messages.stories.tsx @@ -4,7 +4,7 @@ import { expect, userEvent, within } from "storybook/test"; import type { IssueQueuedCommentEntry, IssueQueuedCommentQueue } from "@paperclipai/shared"; import { TaskChatComposer } from "@/components/task-chat/TaskChatComposer"; import { TaskChatQueuedMessages } from "@/components/task-chat/TaskChatQueuedMessages"; -import { composerAgentAppearance, composerAgents } from "../prototypes/composer-model-picker/fixtures"; +import { composerAgentAppearance, composerAgents } from "../../prototypes/composer-model-picker/fixtures"; const agentMap = new Map(composerAgents.map((agent) => [agent.id, { id: agent.id, @@ -127,7 +127,8 @@ function QueuedComposer({ protocol, steeringDisposition, initialEdit, mobile }: } const meta = { - title: "Composer/Steering and queued messages", + id: "composer-steering-and-queued-messages", + title: "Tasks and conversations/Steering and queued messages/Composer/Steering and queued messages", component: QueuedComposer, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/interrupt-handoff.stories.tsx b/ui/storybook/stories/steering/interrupt-handoff.stories.tsx similarity index 98% rename from ui/storybook/stories/interrupt-handoff.stories.tsx rename to ui/storybook/stories/steering/interrupt-handoff.stories.tsx index 9c1f2f0c9dc..dd15023b494 100644 --- a/ui/storybook/stories/interrupt-handoff.stories.tsx +++ b/ui/storybook/stories/steering/interrupt-handoff.stories.tsx @@ -101,7 +101,8 @@ function ActivityCard({ } const meta: Meta = { - title: "Surfaces/Interrupt Handoff", + id: "surfaces-interrupt-handoff", + title: "Tasks and conversations/Steering and queued messages/Surfaces/Interrupt Handoff", }; export default meta; diff --git a/ui/storybook/stories/paused-composer.stories.tsx b/ui/storybook/stories/steering/paused-composer.stories.tsx similarity index 85% rename from ui/storybook/stories/paused-composer.stories.tsx rename to ui/storybook/stories/steering/paused-composer.stories.tsx index 83670ad3fa4..e5582e64078 100644 --- a/ui/storybook/stories/paused-composer.stories.tsx +++ b/ui/storybook/stories/steering/paused-composer.stories.tsx @@ -1,8 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { PausedComposerPreview } from "../prototypes/PausedTaskComposer"; +import { PausedComposerPreview } from "../../prototypes/PausedTaskComposer"; const meta = { - title: "Composer/Paused task takeover", + id: "composer-paused-task-takeover", + title: "Tasks and conversations/Steering and queued messages/Composer/Paused task takeover", component: PausedComposerPreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/task-execution-controls.stories.tsx b/ui/storybook/stories/steering/task-execution-controls.stories.tsx similarity index 99% rename from ui/storybook/stories/task-execution-controls.stories.tsx rename to ui/storybook/stories/steering/task-execution-controls.stories.tsx index 27d72d43405..735fff914a2 100644 --- a/ui/storybook/stories/task-execution-controls.stories.tsx +++ b/ui/storybook/stories/steering/task-execution-controls.stories.tsx @@ -318,7 +318,8 @@ function TaskExecutionExample({ } const meta = { - title: "Composer/Execution controls", + id: "composer-execution-controls", + title: "Tasks and conversations/Steering and queued messages/Composer/Execution controls", component: TaskExecutionExample, parameters: { layout: "padded" }, } satisfies Meta; diff --git a/ui/storybook/stories/issue-review-panel.stories.tsx b/ui/storybook/stories/task-coordination/issue-review-panel.stories.tsx similarity index 95% rename from ui/storybook/stories/issue-review-panel.stories.tsx rename to ui/storybook/stories/task-coordination/issue-review-panel.stories.tsx index 7aada79bd05..22638d9abf0 100644 --- a/ui/storybook/stories/issue-review-panel.stories.tsx +++ b/ui/storybook/stories/task-coordination/issue-review-panel.stories.tsx @@ -83,7 +83,8 @@ const stalledReviewRow: AttentionItem = { }; const meta = { - title: "Product/Decisions/Stalled review actions", + id: "product-decisions-stalled-review-actions", + title: "Tasks and conversations/Delegation, dependencies, and signoff/Product/Decisions/Stalled review actions", component: AttentionQueueRow, args: { item: stalledReviewRow, diff --git a/ui/storybook/stories/monitor-surfaces.stories.tsx b/ui/storybook/stories/task-coordination/monitor-surfaces.stories.tsx similarity index 98% rename from ui/storybook/stories/monitor-surfaces.stories.tsx rename to ui/storybook/stories/task-coordination/monitor-surfaces.stories.tsx index 92bc8b219d1..09357f19d7d 100644 --- a/ui/storybook/stories/monitor-surfaces.stories.tsx +++ b/ui/storybook/stories/task-coordination/monitor-surfaces.stories.tsx @@ -6,7 +6,7 @@ import { storybookExecutionWorkspaces, storybookIssueDocuments, storybookIssues, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const issueDocumentSummaries = storybookIssueDocuments.map(({ body: _body, ...summary }) => summary); @@ -310,7 +310,8 @@ function MonitorSurfaceStories() { } const meta = { - title: "Product/Issue Monitor surfaces", + id: "product-issue-monitor-surfaces", + title: "Tasks and conversations/Delegation, dependencies, and signoff/Product/Issue Monitor surfaces", component: MonitorSurfaceStories, parameters: { docs: { diff --git a/ui/storybook/stories/originating-tasks.stories.tsx b/ui/storybook/stories/task-coordination/originating-tasks.stories.tsx similarity index 91% rename from ui/storybook/stories/originating-tasks.stories.tsx rename to ui/storybook/stories/task-coordination/originating-tasks.stories.tsx index 4d4ee451945..a7932689b58 100644 --- a/ui/storybook/stories/originating-tasks.stories.tsx +++ b/ui/storybook/stories/task-coordination/originating-tasks.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { PluginLauncherProvider } from "@/plugins/launchers"; -import { OriginatingTasksReview, SeedData } from "../prototypes/originating-tasks/OriginatingTasks"; +import { OriginatingTasksReview, SeedData } from "../../prototypes/originating-tasks/OriginatingTasks"; const meta = { - title: "UX Labs/Tasks Created From a Task", + id: "ux-labs-tasks-created-from-a-task", + title: "Tasks and conversations/Delegation, dependencies, and signoff/UX Labs/Tasks Created From a Task", component: OriginatingTasksReview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/sub-issues-workflow.stories.tsx b/ui/storybook/stories/task-coordination/sub-issues-workflow.stories.tsx similarity index 97% rename from ui/storybook/stories/sub-issues-workflow.stories.tsx rename to ui/storybook/stories/task-coordination/sub-issues-workflow.stories.tsx index 4f4132d440c..c35e4874b8b 100644 --- a/ui/storybook/stories/sub-issues-workflow.stories.tsx +++ b/ui/storybook/stories/task-coordination/sub-issues-workflow.stories.tsx @@ -11,7 +11,7 @@ import { storybookCompanies, storybookIssueLabels, storybookProjects, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const companyId = "company-storybook"; const parentId = "issue-pap-1953"; @@ -254,7 +254,8 @@ function SubIssuesWorkflowPanel() { } const meta = { - title: "UX Labs/Sub-issues Workflow Checklist", + id: "ux-labs-sub-issues-workflow-checklist", + title: "Tasks and conversations/Delegation, dependencies, and signoff/UX Labs/Sub-issues Workflow Checklist", component: SubIssuesWorkflowPanel, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/task-watchdog-surfaces.stories.tsx b/ui/storybook/stories/task-coordination/task-watchdog-surfaces.stories.tsx similarity index 95% rename from ui/storybook/stories/task-watchdog-surfaces.stories.tsx rename to ui/storybook/stories/task-coordination/task-watchdog-surfaces.stories.tsx index 0ca142fa57b..3530f65f1db 100644 --- a/ui/storybook/stories/task-watchdog-surfaces.stories.tsx +++ b/ui/storybook/stories/task-coordination/task-watchdog-surfaces.stories.tsx @@ -5,7 +5,7 @@ import { storybookExecutionWorkspaces, storybookIssueDocuments, storybookIssues, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const issueDocumentSummaries = storybookIssueDocuments.map(({ body: _body, ...summary }) => summary); @@ -102,7 +102,8 @@ function WatchdogSurfaceStories() { } const meta = { - title: "Product/Task Watchdog surfaces", + id: "product-task-watchdog-surfaces", + title: "Tasks and conversations/Delegation, dependencies, and signoff/Product/Task Watchdog surfaces", component: WatchdogSurfaceStories, parameters: { docs: { diff --git a/ui/storybook/stories/assigned-backlog-safeguards.stories.tsx b/ui/storybook/stories/tasks/assigned-backlog-safeguards.stories.tsx similarity index 97% rename from ui/storybook/stories/assigned-backlog-safeguards.stories.tsx rename to ui/storybook/stories/tasks/assigned-backlog-safeguards.stories.tsx index 47f40026620..78680e64901 100644 --- a/ui/storybook/stories/assigned-backlog-safeguards.stories.tsx +++ b/ui/storybook/stories/tasks/assigned-backlog-safeguards.stories.tsx @@ -5,7 +5,7 @@ import type { IssueRelationIssueSummary } from "@paperclipai/shared"; import { IssueAssignedBacklogNotice } from "@/components/IssueAssignedBacklogNotice"; import { IssueBlockedNotice } from "@/components/IssueBlockedNotice"; import { IssueRow } from "@/components/IssueRow"; -import { storybookAgents, createIssue } from "../fixtures/paperclipData"; +import { storybookAgents, createIssue } from "../../fixtures/paperclipData"; const codexAgent = storybookAgents.find((agent) => agent.id === "agent-codex") ?? storybookAgents[0]!; const qaAgent = storybookAgents.find((agent) => agent.id === "agent-qa") ?? storybookAgents[0]!; @@ -205,7 +205,8 @@ function AllStates() { } const meta = { - title: "Paperclip/Assigned Backlog Safeguards", + id: "paperclip-assigned-backlog-safeguards", + title: "Tasks and conversations/Task creation and lifecycle/Paperclip/Assigned Backlog Safeguards", component: AllStates, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/chat-comments.stories.tsx b/ui/storybook/stories/tasks/chat-comments.stories.tsx similarity index 98% rename from ui/storybook/stories/chat-comments.stories.tsx rename to ui/storybook/stories/tasks/chat-comments.stories.tsx index 8762c4d1f1b..ba2bbd8d0cc 100644 --- a/ui/storybook/stories/chat-comments.stories.tsx +++ b/ui/storybook/stories/tasks/chat-comments.stories.tsx @@ -16,7 +16,7 @@ import type { IssueChatTranscriptEntry, } from "@/lib/issue-chat-messages"; import type { IssueTimelineEvent } from "@/lib/issue-timeline-events"; -import { storybookAgentMap, storybookAgents } from "../fixtures/paperclipData"; +import { storybookAgentMap, storybookAgents } from "../../fixtures/paperclipData"; const companyId = "company-storybook"; const projectId = "project-board-ui"; @@ -361,7 +361,7 @@ const liveRunTranscript: TranscriptEntry[] = [ name: "apply_patch", toolUseId: "tool-live-patch", input: { - file: "ui/storybook/stories/chat-comments.stories.tsx", + file: "ui/storybook/stories/tasks/chat-comments.stories.tsx", action: "add fixtures", }, }, @@ -557,7 +557,7 @@ const issueTranscriptsByRunId = new Map [agent.id, { id: agent.id, @@ -108,7 +108,8 @@ function ComposerAddStory({ initialMode, goalAvailable, mobile, mobileContext, f } const meta = { - title: "Composer/Add menu", + id: "composer-add-menu", + title: "Tasks and conversations/Task creation and lifecycle/Composer/Add menu", component: ComposerAddStory, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/composer-mobile-assignee.stories.tsx b/ui/storybook/stories/tasks/composer-mobile-assignee.stories.tsx similarity index 84% rename from ui/storybook/stories/composer-mobile-assignee.stories.tsx rename to ui/storybook/stories/tasks/composer-mobile-assignee.stories.tsx index 1f5f8d70b17..d72f59ff0b7 100644 --- a/ui/storybook/stories/composer-mobile-assignee.stories.tsx +++ b/ui/storybook/stories/tasks/composer-mobile-assignee.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, within } from "storybook/test"; -import { ComposerRunSettingsLiveStory } from "../prototypes/composer-model-picker/ComposerRunSettingsLiveStory"; +import { ComposerRunSettingsLiveStory } from "../../prototypes/composer-model-picker/ComposerRunSettingsLiveStory"; const meta = { - title: "Composer/Mobile assignee picker", + id: "composer-mobile-assignee-picker", + title: "Tasks and conversations/Task creation and lifecycle/Composer/Mobile assignee picker", component: ComposerRunSettingsLiveStory, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/composer-model-picker.stories.tsx b/ui/storybook/stories/tasks/composer-model-picker.stories.tsx similarity index 98% rename from ui/storybook/stories/composer-model-picker.stories.tsx rename to ui/storybook/stories/tasks/composer-model-picker.stories.tsx index b99938b62fe..6f3ff357324 100644 --- a/ui/storybook/stories/composer-model-picker.stories.tsx +++ b/ui/storybook/stories/tasks/composer-model-picker.stories.tsx @@ -1,10 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; -import { ComposerModelPickerPreview } from "../prototypes/composer-model-picker/ComposerModelPickerPreview"; -import { ComposerRunSettingsLiveStory } from "../prototypes/composer-model-picker/ComposerRunSettingsLiveStory"; +import { ComposerModelPickerPreview } from "../../prototypes/composer-model-picker/ComposerModelPickerPreview"; +import { ComposerRunSettingsLiveStory } from "../../prototypes/composer-model-picker/ComposerRunSettingsLiveStory"; const meta = { - title: "Composer/Model and effort picker", + id: "composer-model-and-effort-picker", + title: "Tasks and conversations/Task creation and lifecycle/Composer/Model and effort picker", component: ComposerModelPickerPreview, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/control-plane-surfaces.stories.tsx b/ui/storybook/stories/tasks/control-plane-surfaces.stories.tsx similarity index 98% rename from ui/storybook/stories/control-plane-surfaces.stories.tsx rename to ui/storybook/stories/tasks/control-plane-surfaces.stories.tsx index 98e4a1fc5cb..13b559243f3 100644 --- a/ui/storybook/stories/control-plane-surfaces.stories.tsx +++ b/ui/storybook/stories/tasks/control-plane-surfaces.stories.tsx @@ -18,7 +18,7 @@ import { storybookEntityNameMap, storybookEntityTitleMap, storybookIssues, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; function Section({ eyebrow, @@ -247,7 +247,8 @@ function ControlPlaneSurfaces() { } const meta = { - title: "Product/Control Plane Surfaces", + id: "product-control-plane-surfaces", + title: "Tasks and conversations/Task creation and lifecycle/Product/Control Plane Surfaces", component: ControlPlaneSurfaces, parameters: { docs: { diff --git a/ui/storybook/stories/dialogs-modals.stories.tsx b/ui/storybook/stories/tasks/dialogs-modals.stories.tsx similarity index 99% rename from ui/storybook/stories/dialogs-modals.stories.tsx rename to ui/storybook/stories/tasks/dialogs-modals.stories.tsx index 60d7ba8063b..3eb5327e95f 100644 --- a/ui/storybook/stories/dialogs-modals.stories.tsx +++ b/ui/storybook/stories/tasks/dialogs-modals.stories.tsx @@ -30,7 +30,7 @@ import { storybookIssueLabels, storybookIssues, storybookProjects, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const SELECTED_COMPANY_STORAGE_KEY = "paperclip.selectedCompanyId"; @@ -674,7 +674,8 @@ function PathInstructionsModalStory() { } const meta = { - title: "Product/Dialogs & Modals", + id: "product-dialogs-modals", + title: "Tasks and conversations/Task creation and lifecycle/Product/Dialogs & Modals", parameters: { docs: { description: { diff --git a/ui/storybook/stories/external-objects.stories.tsx b/ui/storybook/stories/tasks/external-objects.stories.tsx similarity index 99% rename from ui/storybook/stories/external-objects.stories.tsx rename to ui/storybook/stories/tasks/external-objects.stories.tsx index e0e91816dae..7e45ad9b13d 100644 --- a/ui/storybook/stories/external-objects.stories.tsx +++ b/ui/storybook/stories/tasks/external-objects.stories.tsx @@ -35,7 +35,7 @@ import { storybookIssueLabels, storybookIssues, storybookProjects, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; function makeObject(args: { id: string; @@ -672,7 +672,8 @@ function IntegrationSurfacesStory() { } const meta = { - title: "Foundations/External Objects", + id: "foundations-external-objects", + title: "Tasks and conversations/Task creation and lifecycle/External Objects", component: StateMatrixStory, parameters: { docs: { diff --git a/ui/storybook/stories/issue-management.stories.tsx b/ui/storybook/stories/tasks/issue-management.stories.tsx similarity index 99% rename from ui/storybook/stories/issue-management.stories.tsx rename to ui/storybook/stories/tasks/issue-management.stories.tsx index e1774d82d92..ae060fdb15d 100644 --- a/ui/storybook/stories/issue-management.stories.tsx +++ b/ui/storybook/stories/tasks/issue-management.stories.tsx @@ -49,7 +49,7 @@ import { storybookIssueRuns, storybookIssues, storybookProjects, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const companyId = "company-storybook"; const issueListViewKey = "storybook:issue-management:list"; @@ -872,7 +872,8 @@ function IssueManagementStories() { } const meta = { - title: "Product/Issue Management", + id: "product-issue-management", + title: "Tasks and conversations/Task creation and lifecycle/Product/Issue Management", component: IssueManagementStories, parameters: { docs: { diff --git a/ui/storybook/stories/mobile-entity-pickers.stories.tsx b/ui/storybook/stories/tasks/mobile-entity-pickers.stories.tsx similarity index 94% rename from ui/storybook/stories/mobile-entity-pickers.stories.tsx rename to ui/storybook/stories/tasks/mobile-entity-pickers.stories.tsx index ea06c277d49..f01c3efadc0 100644 --- a/ui/storybook/stories/mobile-entity-pickers.stories.tsx +++ b/ui/storybook/stories/tasks/mobile-entity-pickers.stories.tsx @@ -41,7 +41,8 @@ function OpenPicker({ kind, options }: { kind: "Assignee" | "Project"; options: } const meta = { - title: "Components/Entity pickers/Mobile", + id: "components-entity-pickers-mobile", + title: "Tasks and conversations/Task creation and lifecycle/Components/Entity pickers/Mobile", parameters: { layout: "fullscreen", viewport: { defaultViewport: "mobile1" }, diff --git a/ui/storybook/stories/new-task-composer.stories.tsx b/ui/storybook/stories/tasks/new-task-composer.stories.tsx similarity index 99% rename from ui/storybook/stories/new-task-composer.stories.tsx rename to ui/storybook/stories/tasks/new-task-composer.stories.tsx index 73fd9a8275d..5cebf9df7b6 100644 --- a/ui/storybook/stories/new-task-composer.stories.tsx +++ b/ui/storybook/stories/tasks/new-task-composer.stories.tsx @@ -25,7 +25,7 @@ import { storybookExecutionWorkspaces, storybookProjects, storybookIssues, -} from "../fixtures/paperclipData"; +} from "../../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; const NEW_TASK_AGENTS = [...storybookAgents, { ...storybookAgents[2]!, id: "agent-ceo", name: "CEO", role: "ceo" }].map((agent) => @@ -203,7 +203,8 @@ function NewTaskStory({ } const meta = { - title: "Composer/New task", + id: "composer-new-task", + title: "Tasks and conversations/Task creation and lifecycle/Composer/New task", component: NewTaskStory, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/runner-activity.stories.tsx b/ui/storybook/stories/tasks/runner-activity.stories.tsx similarity index 93% rename from ui/storybook/stories/runner-activity.stories.tsx rename to ui/storybook/stories/tasks/runner-activity.stories.tsx index 17aea163b4f..829817578a9 100644 --- a/ui/storybook/stories/runner-activity.stories.tsx +++ b/ui/storybook/stories/tasks/runner-activity.stories.tsx @@ -1,9 +1,10 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { MINIMAL_VIEWPORTS } from "storybook/viewport"; -import { RunnerActivityPreview } from "../prototypes/runner-activity/RunnerActivityPreview"; +import { RunnerActivityPreview } from "../../prototypes/runner-activity/RunnerActivityPreview"; const meta = { - title: "Tasks/Runner activity preview", + id: "tasks-runner-activity-preview", + title: "Tasks and conversations/Task creation and lifecycle/Tasks/Runner activity preview", component: RunnerActivityPreview, globals: { viewport: { value: "desktop", isRotated: false } }, parameters: { diff --git a/ui/storybook/stories/successful-run-handoff.stories.tsx b/ui/storybook/stories/tasks/successful-run-handoff.stories.tsx similarity index 97% rename from ui/storybook/stories/successful-run-handoff.stories.tsx rename to ui/storybook/stories/tasks/successful-run-handoff.stories.tsx index 18085a7dee4..5044a46281d 100644 --- a/ui/storybook/stories/successful-run-handoff.stories.tsx +++ b/ui/storybook/stories/tasks/successful-run-handoff.stories.tsx @@ -13,7 +13,7 @@ import { SUCCESSFUL_RUN_HANDOFF_RESOLVED_ACTION, successfulRunHandoffActivityTone, } from "@/lib/successful-run-handoff"; -import { createIssue, storybookAgents } from "../fixtures/paperclipData"; +import { createIssue, storybookAgents } from "../../fixtures/paperclipData"; function ActivityExample({ action }: { action: string }) { const tone = successfulRunHandoffActivityTone(action); @@ -238,7 +238,8 @@ function SuccessfulRunHandoffEscalationComment() { } const meta = { - title: "Paperclip/Successful Run Handoff", + id: "paperclip-successful-run-handoff", + title: "Tasks and conversations/Task creation and lifecycle/Paperclip/Successful Run Handoff", component: SuccessfulRunHandoffStates, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/task-chat-chain-of-thought.stories.tsx b/ui/storybook/stories/tasks/task-chat-chain-of-thought.stories.tsx similarity index 97% rename from ui/storybook/stories/task-chat-chain-of-thought.stories.tsx rename to ui/storybook/stories/tasks/task-chat-chain-of-thought.stories.tsx index 4719d135fca..a2346dab7dd 100644 --- a/ui/storybook/stories/task-chat-chain-of-thought.stories.tsx +++ b/ui/storybook/stories/tasks/task-chat-chain-of-thought.stories.tsx @@ -159,7 +159,8 @@ function ReconnectingAlignmentReview() { } const meta = { - title: "Tasks/Task chat review fixes", + id: "tasks-task-chat-review-fixes", + title: "Tasks and conversations/Task creation and lifecycle/Tasks/Task chat review fixes", component: ChainOfThoughtReview, parameters: { layout: "padded" }, } satisfies Meta; diff --git a/ui/storybook/stories/task-chat-mobile-follow.stories.tsx b/ui/storybook/stories/tasks/task-chat-mobile-follow.stories.tsx similarity index 95% rename from ui/storybook/stories/task-chat-mobile-follow.stories.tsx rename to ui/storybook/stories/tasks/task-chat-mobile-follow.stories.tsx index 1fb4d9b3cad..9454036c6e6 100644 --- a/ui/storybook/stories/task-chat-mobile-follow.stories.tsx +++ b/ui/storybook/stories/tasks/task-chat-mobile-follow.stories.tsx @@ -44,7 +44,8 @@ function MobileFollow() { } const meta = { - title: "Task chat/Mobile streaming follow", + id: "task-chat-mobile-streaming-follow", + title: "Tasks and conversations/Task creation and lifecycle/Task chat/Mobile streaming follow", component: MobileFollow, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/task-chat-system-notice.stories.tsx b/ui/storybook/stories/tasks/task-chat-system-notice.stories.tsx similarity index 94% rename from ui/storybook/stories/task-chat-system-notice.stories.tsx rename to ui/storybook/stories/tasks/task-chat-system-notice.stories.tsx index 553e8e24de4..13c138382d6 100644 --- a/ui/storybook/stories/task-chat-system-notice.stories.tsx +++ b/ui/storybook/stories/tasks/task-chat-system-notice.stories.tsx @@ -53,7 +53,8 @@ function StoryFrame({ item }: { item: TaskChatMessageItem }) { } const meta = { - title: "Product/Task chat/System notice", + id: "product-task-chat-system-notice", + title: "Tasks and conversations/Task creation and lifecycle/Product/Task chat/System notice", component: TaskChatSystemNotice, args: { item: workspaceReadyItem, diff --git a/ui/storybook/stories/task-context-notices.stories.tsx b/ui/storybook/stories/tasks/task-context-notices.stories.tsx similarity index 96% rename from ui/storybook/stories/task-context-notices.stories.tsx rename to ui/storybook/stories/tasks/task-context-notices.stories.tsx index f92b8ffaaac..945085fb208 100644 --- a/ui/storybook/stories/task-context-notices.stories.tsx +++ b/ui/storybook/stories/tasks/task-context-notices.stories.tsx @@ -45,7 +45,8 @@ function TaskContextNotices() { } const meta = { - title: "Tasks/Context notices", + id: "tasks-context-notices", + title: "Tasks and conversations/Task creation and lifecycle/Tasks/Context notices", component: TaskContextNotices, parameters: { layout: "fullscreen" }, } satisfies Meta; diff --git a/ui/storybook/stories/thread-human-identity/00-evidence.stories.tsx b/ui/storybook/stories/tasks/thread-human-identity/00-evidence.stories.tsx similarity index 85% rename from ui/storybook/stories/thread-human-identity/00-evidence.stories.tsx rename to ui/storybook/stories/tasks/thread-human-identity/00-evidence.stories.tsx index 13089428aeb..db6f71466bf 100644 --- a/ui/storybook/stories/thread-human-identity/00-evidence.stories.tsx +++ b/ui/storybook/stories/tasks/thread-human-identity/00-evidence.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { humanMessages, profiles } from "./fixtures"; +import { humanMessages, profiles } from "../../../support/thread-human-identity/fixtures"; function ReviewGuide() { return

Human identity in a mixed task conversation

@@ -10,5 +10,6 @@ function ReviewGuide() {

These are representative fixtures, not a copy of private task data. The mixed journey uses the production shell, adapter, thread, and composer. Swap the viewer to verify that ownership follows authorUserId. Full Conversation includes all fixture messages. Sending is local to the preview.

; } -export default { title: "Chat & Comments/Human identity/00 Review guide", component: ReviewGuide } satisfies Meta; +export default { id: "chat-comments-human-identity-00-review-guide", + title: "Tasks and conversations/Task creation and lifecycle/Chat & Comments/Human identity/00 Review guide", component: ReviewGuide } satisfies Meta; export const DataFlow: StoryObj = {}; diff --git a/ui/storybook/stories/thread-human-identity/01-components.stories.tsx b/ui/storybook/stories/tasks/thread-human-identity/01-components.stories.tsx similarity index 88% rename from ui/storybook/stories/thread-human-identity/01-components.stories.tsx rename to ui/storybook/stories/tasks/thread-human-identity/01-components.stories.tsx index db5af49ab97..f95ac6e2b64 100644 --- a/ui/storybook/stories/thread-human-identity/01-components.stories.tsx +++ b/ui/storybook/stories/tasks/thread-human-identity/01-components.stories.tsx @@ -3,7 +3,7 @@ import { expect, within } from "storybook/test"; import { TaskChatBubble } from "@/components/task-chat/TaskChatBubble"; import { commentsToTaskChatItems } from "@/components/task-chat/task-chat-adapter"; import type { TaskChatMessageItem } from "@/components/task-chat/task-chat-model"; -import { comments, profiles, alexId, samId } from "./fixtures"; +import { comments, profiles, alexId, samId } from "../../../support/thread-human-identity/fixtures"; function Bubble({ viewer = alexId, photo = null, unresolved = false }: { viewer?: string; photo?: string | null; unresolved?: boolean }) { const userProfileMap = new Map(profiles); @@ -12,7 +12,8 @@ function Bubble({ viewer = alexId, photo = null, unresolved = false }: { viewer? return
; } const meta = { - title: "Chat & Comments/Human identity/01 Components", component: Bubble, + id: "chat-comments-human-identity-01-components", + title: "Tasks and conversations/Task creation and lifecycle/Chat & Comments/Human identity/01 Components", component: Bubble, args: { viewer: alexId, photo: null }, parameters: { layout: "padded", docs: { description: { component: "Production adapter → model → TaskChatBubble → Identity. Other humans sit on the left on the same page surface as agents with their own name/photo; no-photo profiles use the shared initials fallback. Edit photo to exercise an uploaded Paperclip asset URL. External URLs use initials. The production avatar URL is taken from the company directory, never from the current viewer or the relay bot." } } }, } satisfies Meta; diff --git a/ui/storybook/stories/thread-human-identity/02-task-journey.stories.tsx b/ui/storybook/stories/tasks/thread-human-identity/02-task-journey.stories.tsx similarity index 93% rename from ui/storybook/stories/thread-human-identity/02-task-journey.stories.tsx rename to ui/storybook/stories/tasks/thread-human-identity/02-task-journey.stories.tsx index ae6dd2699b0..0fea4b3e70b 100644 --- a/ui/storybook/stories/thread-human-identity/02-task-journey.stories.tsx +++ b/ui/storybook/stories/tasks/thread-human-identity/02-task-journey.stories.tsx @@ -7,8 +7,8 @@ import { Layout } from "@/components/Layout"; import { TaskChatThread } from "@/components/TaskChatThread"; import { PluginLauncherProvider } from "@/plugins/launchers"; import { queryKeys } from "@/lib/queryKeys"; -import { storybookAuthSession } from "../../fixtures/paperclipData"; -import { mixedComments as initialComments, profiles, alexId, samId, issueId, agentMap } from "./fixtures"; +import { storybookAuthSession } from "../../../fixtures/paperclipData"; +import { mixedComments as initialComments, profiles, alexId, samId, issueId, agentMap } from "../../../support/thread-human-identity/fixtures"; function Conversation({ viewer = alexId, fullHistory = false }: { viewer?: string; fullHistory?: boolean }) { const [comments, setComments] = useState(() => fullHistory ? initialComments : [...initialComments.slice(0, 2), ...initialComments.slice(-2)]); @@ -43,7 +43,8 @@ function Journey(props: { viewer?: string; fullHistory?: boolean }) {
; } const meta = { - title: "Chat & Comments/Human identity/02 Mixed-author journey", component: Journey, + id: "chat-comments-human-identity-02-mixed-author-journey", + title: "Tasks and conversations/Task creation and lifecycle/Chat & Comments/Human identity/02 Mixed-author journey", component: Journey, args: { viewer: alexId, fullHistory: false }, parameters: { layout: "fullscreen", initialEntries: ["/PAP/issues/human-identity-example"], docs: { story: { inline: false }, description: { component: "Representative turns from two humans and an agent in the production app shell and TaskChatThread, including its composer. Photos remain null so the shared initials fallback is visible. Swap the viewer: identity follows authorUserId. The default excerpt fits all three speakers in one view; Full Conversation includes all fixture messages. Sending is local to this preview." } } }, } satisfies Meta; diff --git a/ui/storybook/stories/team-catalog.stories.tsx b/ui/storybook/stories/teams/team-catalog.stories.tsx similarity index 99% rename from ui/storybook/stories/team-catalog.stories.tsx rename to ui/storybook/stories/teams/team-catalog.stories.tsx index 922f1cf304b..f4359fb4b38 100644 --- a/ui/storybook/stories/team-catalog.stories.tsx +++ b/ui/storybook/stories/teams/team-catalog.stories.tsx @@ -139,7 +139,8 @@ function Frame({ children, width = "max-w-3xl" }: { children: React.ReactNode; w } const meta: Meta = { - title: "Surfaces/Team Catalog", + id: "surfaces-team-catalog", + title: "Agents and reusable capabilities/Team packages and installation/Surfaces/Team Catalog", }; export default meta; type Story = StoryObj; diff --git a/ui/storybook/stories/browser-use-activity.stories.tsx b/ui/storybook/stories/workspaces/browser-use-activity.stories.tsx similarity index 94% rename from ui/storybook/stories/browser-use-activity.stories.tsx rename to ui/storybook/stories/workspaces/browser-use-activity.stories.tsx index 1554a7a8b64..bd359555029 100644 --- a/ui/storybook/stories/browser-use-activity.stories.tsx +++ b/ui/storybook/stories/workspaces/browser-use-activity.stories.tsx @@ -8,9 +8,10 @@ import { BrowserStoryProviders, InteractiveBrowserStory, mockBrowserUse, -} from "../fixtures/browser-use"; +} from "../../fixtures/browser-use"; const meta = { - title: "Tasks/Browser Use/Task activity", + id: "tasks-browser-use-task-activity", + title: "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity", component: TaskBrowserActivity, parameters: { layout: "centered", diff --git a/ui/storybook/stories/browser-use-footer.stories.tsx b/ui/storybook/stories/workspaces/browser-use-footer.stories.tsx similarity index 96% rename from ui/storybook/stories/browser-use-footer.stories.tsx rename to ui/storybook/stories/workspaces/browser-use-footer.stories.tsx index f62b4f75a5d..a51134420ff 100644 --- a/ui/storybook/stories/browser-use-footer.stories.tsx +++ b/ui/storybook/stories/workspaces/browser-use-footer.stories.tsx @@ -7,7 +7,7 @@ import type { TaskBrowser, } from "@paperclipai/shared"; import { TaskBrowserFooter } from "@/components/task-side-panel/TaskBrowserFooter"; -import { browserFixture } from "../fixtures/browser-use"; +import { browserFixture } from "../../fixtures/browser-use"; const now = Date.parse("2026-09-29T16:00:00Z"); const idle = (seconds: number): TaskBrowser => ({ ...browserFixture, @@ -15,7 +15,8 @@ const idle = (seconds: number): TaskBrowser => ({ idleDeadline: new Date(now + seconds * 1000).toISOString(), }); const meta = { - title: "Tasks/Browser Use/Footer", + id: "tasks-browser-use-footer", + title: "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer", component: TaskBrowserFooter, parameters: { layout: "centered", diff --git a/ui/storybook/stories/browser-use.stories.tsx b/ui/storybook/stories/workspaces/browser-use.stories.tsx similarity index 97% rename from ui/storybook/stories/browser-use.stories.tsx rename to ui/storybook/stories/workspaces/browser-use.stories.tsx index 608054f1dbc..f132beed510 100644 --- a/ui/storybook/stories/browser-use.stories.tsx +++ b/ui/storybook/stories/workspaces/browser-use.stories.tsx @@ -4,17 +4,18 @@ import { expect, userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { TaskSidePanel } from "@/components/task-side-panel/TaskSidePanel"; import { TaskBrowserPanel } from "@/components/task-side-panel/TaskBrowserPanel"; -import { storybookIssues } from "../fixtures/paperclipData"; +import { storybookIssues } from "../../fixtures/paperclipData"; import { BrowserStoryProviders, InteractiveBrowserStory, browserFixture, browserState, mockBrowserUse, -} from "../fixtures/browser-use"; +} from "../../fixtures/browser-use"; const meta = { - title: "Tasks/Browser Use/Panel", + id: "tasks-browser-use-panel", + title: "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel", component: TaskBrowserPanel, parameters: { layout: "fullscreen", diff --git a/ui/storybook/stories/searchable-select.stories.tsx b/ui/storybook/stories/workspaces/searchable-select.stories.tsx similarity index 98% rename from ui/storybook/stories/searchable-select.stories.tsx rename to ui/storybook/stories/workspaces/searchable-select.stories.tsx index 12d657245e8..1600f40fcaa 100644 --- a/ui/storybook/stories/searchable-select.stories.tsx +++ b/ui/storybook/stories/workspaces/searchable-select.stories.tsx @@ -185,7 +185,8 @@ function FormContext({ triggerClassName }: { triggerClassName?: string }) { } const meta = { - title: "Components/SearchableSelect/Workspace picker", + id: "components-searchableselect-workspace-picker", + title: "Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker", parameters: { layout: "centered" }, } satisfies Meta; diff --git a/ui/storybook/stories/workspace-file-browser.stories.tsx b/ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx similarity index 98% rename from ui/storybook/stories/workspace-file-browser.stories.tsx rename to ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx index b33d8eda98f..72bc848e765 100644 --- a/ui/storybook/stories/workspace-file-browser.stories.tsx +++ b/ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx @@ -197,7 +197,8 @@ function Placement() { } const meta: Meta = { - title: "Issue/Workspace File Browser", + id: "issue-workspace-file-browser", + title: "Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser", parameters: { layout: "fullscreen" }, }; export default meta; diff --git a/ui/storybook/stories/workspace-runtime-provisioning.stories.tsx b/ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx similarity index 95% rename from ui/storybook/stories/workspace-runtime-provisioning.stories.tsx rename to ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx index edfe1f6ccf6..7dfdcc6c46d 100644 --- a/ui/storybook/stories/workspace-runtime-provisioning.stories.tsx +++ b/ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx @@ -37,7 +37,8 @@ function ContextCard({ status }: { status: RuntimeProvisionStatus }) { } const meta: Meta = { - title: "Workspaces/Runtime provisioning status", + id: "workspaces-runtime-provisioning-status", + title: "Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status", component: RuntimeProvisionStatusValue, parameters: { layout: "padded" }, }; diff --git a/ui/storybook/stories/workspace-service-control-bar.stories.tsx b/ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx similarity index 97% rename from ui/storybook/stories/workspace-service-control-bar.stories.tsx rename to ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx index 62aef3243a3..f542218f387 100644 --- a/ui/storybook/stories/workspace-service-control-bar.stories.tsx +++ b/ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx @@ -20,7 +20,8 @@ function entry(overrides: Partial = {}): Workspace } const meta: Meta = { - title: "Workspaces/Service control bar", + id: "workspaces-service-control-bar", + title: "Projects and execution/Workspaces, services, and files/Workspaces/Service control bar", component: WorkspaceServiceControlBar, parameters: { layout: "padded", diff --git a/ui/storybook/stories/artifact-cards/README.md b/ui/storybook/support/artifact-cards/README.md similarity index 96% rename from ui/storybook/stories/artifact-cards/README.md rename to ui/storybook/support/artifact-cards/README.md index 76d9d8f0d5b..6734b88c4be 100644 --- a/ui/storybook/stories/artifact-cards/README.md +++ b/ui/storybook/support/artifact-cards/README.md @@ -1,6 +1,6 @@ # Artifact cards -Storybook: **Explorations → Artifact cards**. Each story renders one production +Storybook: **Tasks and conversations → Documents, attachments, and work products → Explorations → Artifact cards**. Each story renders one production card from `ui/src/components/artifacts/RichArtifactCards.tsx`. All example titles, authors, content, filenames, repository names, and URLs live in story args. Use **Controls** to edit values, including Markdown bodies and JSON table data. diff --git a/ui/storybook/stories/artifact-cards/storyConfig.tsx b/ui/storybook/support/artifact-cards/storyConfig.tsx similarity index 100% rename from ui/storybook/stories/artifact-cards/storyConfig.tsx rename to ui/storybook/support/artifact-cards/storyConfig.tsx diff --git a/ui/storybook/stories/artifact-upload/UploadPreview.tsx b/ui/storybook/support/artifact-upload/UploadPreview.tsx similarity index 100% rename from ui/storybook/stories/artifact-upload/UploadPreview.tsx rename to ui/storybook/support/artifact-upload/UploadPreview.tsx diff --git a/ui/storybook/stories/artifact-upload/fixtures.ts b/ui/storybook/support/artifact-upload/fixtures.ts similarity index 100% rename from ui/storybook/stories/artifact-upload/fixtures.ts rename to ui/storybook/support/artifact-upload/fixtures.ts diff --git a/ui/storybook/stories/credential-access/CredentialTaskPage.tsx b/ui/storybook/support/credential-access/CredentialTaskPage.tsx similarity index 100% rename from ui/storybook/stories/credential-access/CredentialTaskPage.tsx rename to ui/storybook/support/credential-access/CredentialTaskPage.tsx diff --git a/ui/storybook/stories/decision-models/fixtures.ts b/ui/storybook/support/decision-models/fixtures.ts similarity index 100% rename from ui/storybook/stories/decision-models/fixtures.ts rename to ui/storybook/support/decision-models/fixtures.ts diff --git a/ui/storybook/stories/external-agent-invite/ExternalAgentInvitePreview.tsx b/ui/storybook/support/external-agent-invite/ExternalAgentInvitePreview.tsx similarity index 100% rename from ui/storybook/stories/external-agent-invite/ExternalAgentInvitePreview.tsx rename to ui/storybook/support/external-agent-invite/ExternalAgentInvitePreview.tsx diff --git a/ui/storybook/stories/external-agent-invite/fixtures.ts b/ui/storybook/support/external-agent-invite/fixtures.ts similarity index 100% rename from ui/storybook/stories/external-agent-invite/fixtures.ts rename to ui/storybook/support/external-agent-invite/fixtures.ts diff --git a/ui/storybook/stories/github-bot-management/fixture-api.test.tsx b/ui/storybook/support/github-bot-management/fixture-api.test.tsx similarity index 100% rename from ui/storybook/stories/github-bot-management/fixture-api.test.tsx rename to ui/storybook/support/github-bot-management/fixture-api.test.tsx diff --git a/ui/storybook/stories/github-bot-management/fixture-api.tsx b/ui/storybook/support/github-bot-management/fixture-api.tsx similarity index 100% rename from ui/storybook/stories/github-bot-management/fixture-api.tsx rename to ui/storybook/support/github-bot-management/fixture-api.tsx diff --git a/ui/storybook/stories/github-bot-management/fixtures.ts b/ui/storybook/support/github-bot-management/fixtures.ts similarity index 100% rename from ui/storybook/stories/github-bot-management/fixtures.ts rename to ui/storybook/support/github-bot-management/fixtures.ts diff --git a/ui/storybook/stories/html-artifacts/fixtures.ts b/ui/storybook/support/html-artifacts/fixtures.ts similarity index 100% rename from ui/storybook/stories/html-artifacts/fixtures.ts rename to ui/storybook/support/html-artifacts/fixtures.ts diff --git a/ui/storybook/stories/html-artifacts/report.html b/ui/storybook/support/html-artifacts/report.html similarity index 100% rename from ui/storybook/stories/html-artifacts/report.html rename to ui/storybook/support/html-artifacts/report.html diff --git a/ui/storybook/stories/primary-agent/PrimaryAgentStory.tsx b/ui/storybook/support/primary-agent/PrimaryAgentStory.tsx similarity index 100% rename from ui/storybook/stories/primary-agent/PrimaryAgentStory.tsx rename to ui/storybook/support/primary-agent/PrimaryAgentStory.tsx diff --git a/ui/storybook/stories/private-tasks/PrivacyStory.tsx b/ui/storybook/support/private-tasks/PrivacyStory.tsx similarity index 100% rename from ui/storybook/stories/private-tasks/PrivacyStory.tsx rename to ui/storybook/support/private-tasks/PrivacyStory.tsx diff --git a/ui/storybook/stories/public-mcp-presenter.tsx b/ui/storybook/support/public-mcp-presenter.tsx similarity index 100% rename from ui/storybook/stories/public-mcp-presenter.tsx rename to ui/storybook/support/public-mcp-presenter.tsx diff --git a/ui/storybook/stories/repository-skill-packages/README.md b/ui/storybook/support/repository-skill-packages/README.md similarity index 68% rename from ui/storybook/stories/repository-skill-packages/README.md rename to ui/storybook/support/repository-skill-packages/README.md index 738b47bd2c1..ffe1e47b799 100644 --- a/ui/storybook/stories/repository-skill-packages/README.md +++ b/ui/storybook/support/repository-skill-packages/README.md @@ -1,6 +1,6 @@ # Repository imports -Run the UI Storybook, then open **Skills → Repository packages**. These stories use the production pages and components with scoped API fixtures; they do not download GitHub repositories or invoke agents. +Run the UI Storybook, then open **Agents and reusable capabilities → Skills and Skill Studio → Skills → Repository packages**. These stories use the production pages and components with scoped API fixtures; they do not download GitHub repositories or invoke agents. - **01–03**: normal repository import and shared-file inspection. Every skill is visible and selected; package files start collapsed. - **04–05**: omitted files, collapsed and expanded warnings; import stays available. diff --git a/ui/storybook/stories/thread-human-identity/fixtures.ts b/ui/storybook/support/thread-human-identity/fixtures.ts similarity index 100% rename from ui/storybook/stories/thread-human-identity/fixtures.ts rename to ui/storybook/support/thread-human-identity/fixtures.ts diff --git a/ui/storybook/stories/workspace-branch-recovery/fixtures.ts b/ui/storybook/support/workspace-branch-recovery/fixtures.ts similarity index 100% rename from ui/storybook/stories/workspace-branch-recovery/fixtures.ts rename to ui/storybook/support/workspace-branch-recovery/fixtures.ts From 9b1b905e01445d1d46228ff8c9e45e3a8ab6c96a Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 15:44:47 -0700 Subject: [PATCH 09/37] fix(heartbeat): answer an unsettled adapter Stop with a 409 instead of a 500 (#16032) An owned adapter Stop that is still settling when the 60s request wait expires now returns 409 adapter_stop_pending instead of escaping as an HTTP 500. Stop proof, lease and restore ownership are unchanged. Sentry is notified only when the same execution is still unsettled ten minutes after the Stop request (measured from stopRequestedAt), so real Stop hangs stay visible and slow workspace sync-back no longer pages. Co-Authored-By: Paperclip --- .../adapter-execution-control.test.ts | 70 ++++++++++++++++ .../src/services/adapter-execution-control.ts | 67 +++++++++++++--- .../services/heartbeat/run-control.test.ts | 79 ++++++++++++++++++- server/src/services/heartbeat/run-control.ts | 20 ++++- 4 files changed, 222 insertions(+), 14 deletions(-) diff --git a/server/src/services/adapter-execution-control.test.ts b/server/src/services/adapter-execution-control.test.ts index 7167e51f249..cc030434897 100644 --- a/server/src/services/adapter-execution-control.test.ts +++ b/server/src/services/adapter-execution-control.test.ts @@ -5,6 +5,7 @@ import { captureAdapterStopOwnership, createAdapterExecutionControl, registerAdapterExecutionControl, + reportAdapterStopIfUnsettled, waitForAdapterStop, } from "./adapter-execution-control.js"; @@ -204,3 +205,72 @@ it.each(["replaced", "wrong_run", "wrong_promise", "throwing_reader"])("omits pe other.finish(); adapterExecutionControls.delete(runId); }); + +it("reports a Stop once, with its pending phase, only when it stays unsettled", async () => { + vi.useFakeTimers(); + const runId = "11111111-1111-4111-8111-111111111111"; + const control = createAdapterExecutionControl(); + await registerAdapterExecutionControl(runId, control); + const report = vi.fn(); + try { + control.phases.enter("workspace_restore"); + reportAdapterStopIfUnsettled(control.settled, report, { runId, adapterType: "codex_local" }, control, { reportAfterMs: 5000 }); + // A repeated Stop for the same execution must not add another report. + reportAdapterStopIfUnsettled(control.settled, report, { runId }, control, { reportAfterMs: 5000 }); + await vi.advanceTimersByTimeAsync(4999); + expect(report).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1); + expect(report).toHaveBeenCalledOnce(); + const error = report.mock.calls[0]![0] as AdapterStopTimeoutError; + expect(error).toBeInstanceOf(AdapterStopTimeoutError); + expect(error.diagnostics).toMatchObject({ + runId, adapterType: "codex_local", timeoutMs: 5000, phase: "workspace_restore", phaseElapsedMs: expect.any(Number), + }); + expect(vi.getTimerCount()).toBe(0); + expect(adapterExecutionControls.get(runId)).toBe(control); + } finally { + control.finish(); + adapterExecutionControls.delete(runId); + } +}); + +it("does not report a Stop that settles before the report bound", async () => { + vi.useFakeTimers(); + const control = createAdapterExecutionControl(); + const report = vi.fn(); + reportAdapterStopIfUnsettled(control.settled, report, undefined, control, { reportAfterMs: 5000 }); + await vi.advanceTimersByTimeAsync(1000); + control.finish(); + await control.settled; + expect(vi.getTimerCount()).toBe(0); + await vi.advanceTimersByTimeAsync(10_000); + expect(report).not.toHaveBeenCalled(); +}); + +it("measures the report bound from the Stop request, including its response wait", async () => { + vi.useFakeTimers(); + const control = createAdapterExecutionControl(); + const report = vi.fn(); + const stopRequestedAt = Date.now(); + // The Stop request's own response wait elapses before the watch starts. + const responseWait = stopTimeout(waitForAdapterStop(control.settled, 2000)); + await vi.advanceTimersByTimeAsync(2000); + await responseWait; + reportAdapterStopIfUnsettled(control.settled, report, undefined, control, { stopRequestedAt, reportAfterMs: 5000 }); + await vi.advanceTimersByTimeAsync(2999); + expect(report).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1); + expect(report).toHaveBeenCalledOnce(); + expect((report.mock.calls[0]![0] as AdapterStopTimeoutError).diagnostics).toMatchObject({ timeoutMs: 5000 }); + control.finish(); +}); + +it("keeps a failing reporter from escaping its timer", async () => { + vi.useFakeTimers(); + const control = createAdapterExecutionControl(); + const report = vi.fn(() => { throw new Error("reporter down"); }); + reportAdapterStopIfUnsettled(control.settled, report, undefined, undefined, { reportAfterMs: 1000 }); + await vi.advanceTimersByTimeAsync(1000); + expect(report).toHaveBeenCalledOnce(); + control.finish(); +}); diff --git a/server/src/services/adapter-execution-control.ts b/server/src/services/adapter-execution-control.ts index 06f60cab9fb..9b1b9f17d08 100644 --- a/server/src/services/adapter-execution-control.ts +++ b/server/src/services/adapter-execution-control.ts @@ -68,9 +68,9 @@ export async function registerAdapterExecutionControl( adapterExecutionControls.set(runId, control); } -export async function waitForAdapterStop( +function adapterStopTimeoutError( settled: Promise, - timeoutMs = 60_000, + timeoutMs: number, diagnostics?: AdapterStopContext, owner?: ReturnType, ) { @@ -83,21 +83,27 @@ export async function waitForAdapterStop( return null; } }; + return new AdapterStopTimeoutError(timeoutMs, { + ...diagnostics, + phase: undefined, + phaseElapsedMs: undefined, + ...currentPhase(), + }); +} + +export async function waitForAdapterStop( + settled: Promise, + timeoutMs = 60_000, + diagnostics?: AdapterStopContext, + owner?: ReturnType, +) { let timer: ReturnType | undefined; try { await Promise.race([ settled, new Promise((_, reject) => { timer = setTimeout( - () => - reject( - new AdapterStopTimeoutError(timeoutMs, { - ...diagnostics, - phase: undefined, - phaseElapsedMs: undefined, - ...currentPhase(), - }), - ), + () => reject(adapterStopTimeoutError(settled, timeoutMs, diagnostics, owner)), timeoutMs, ); }), @@ -106,3 +112,42 @@ export async function waitForAdapterStop( clearTimeout(timer); } } + +// A Stop request's wait is a response budget, not the adapter's settlement +// bound: workspace sync-back and cooperative provider cancellation routinely +// settle a few minutes after it. Only a Stop still unsettled this long after +// it was requested is an operational failure worth reporting. +export const UNSETTLED_ADAPTER_STOP_REPORT_MS = 10 * 60_000; +const watchedAdapterStops = new WeakSet>(); + +/** Reports at most once per execution, and only if it never settles in time. */ +export function reportAdapterStopIfUnsettled( + settled: Promise, + report: (error: AdapterStopTimeoutError) => void, + diagnostics?: AdapterStopContext, + owner?: ReturnType, + { + stopRequestedAt = Date.now(), + reportAfterMs = UNSETTLED_ADAPTER_STOP_REPORT_MS, + }: { stopRequestedAt?: number; reportAfterMs?: number } = {}, +) { + if (watchedAdapterStops.has(settled)) return; + watchedAdapterStops.add(settled); + let done = false; + // The bound runs from the Stop request, which already spent its response wait. + const remainingMs = Math.max(0, stopRequestedAt + reportAfterMs - Date.now()); + const timer = setTimeout(() => { + if (done) return; + try { + report(adapterStopTimeoutError(settled, reportAfterMs, diagnostics, owner)); + } catch { + // Reporting is diagnostic only. + } + }, remainingMs); + // Never hold process shutdown open for a diagnostic. + timer.unref?.(); + void settled.then(() => { + done = true; + clearTimeout(timer); + }); +} diff --git a/server/src/services/heartbeat/run-control.test.ts b/server/src/services/heartbeat/run-control.test.ts index 0234f326f48..d140f7f234d 100644 --- a/server/src/services/heartbeat/run-control.test.ts +++ b/server/src/services/heartbeat/run-control.test.ts @@ -5,7 +5,15 @@ import { eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js"; import { runningProcesses } from "../../adapters/index.js"; -import { adapterExecutionControls } from "../adapter-execution-control.js"; +import { + adapterExecutionControls, + createAdapterExecutionControl, + reportAdapterStopIfUnsettled, + waitForAdapterStop, +} from "../adapter-execution-control.js"; +import { AdapterStopTimeoutError } from "../adapter-stop-timeout.js"; +import { HttpError } from "../../errors.js"; +import { captureException } from "../../sentry.js"; import { terminateLocalService } from "../local-service-supervisor.js"; import { createHeartbeatRunState } from "./run-state.js"; import { createHeartbeatRunPreparation } from "./run-preparation.js"; @@ -13,6 +21,14 @@ import { createHeartbeatLifecycle, type HeartbeatLifecycleDependencies } from ". import { createHeartbeatRunControl, type HeartbeatRunControlDependencies } from "./run-control.js"; vi.mock("../live-events.js", () => ({ publishLiveEvent: vi.fn() })); +vi.mock("../adapter-execution-control.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + waitForAdapterStop: vi.fn(actual.waitForAdapterStop), + reportAdapterStopIfUnsettled: vi.fn(actual.reportAdapterStopIfUnsettled), + }; +}); vi.mock("../local-service-supervisor.js", async (importOriginal) => ({ ...await importOriginal(), terminateLocalService: vi.fn(async () => undefined), @@ -256,6 +272,67 @@ describe.skipIf(!support.supported)("heartbeat run-control database wiring", () expect(deps.failedProcessRunCancellations.get(run.id)?.error).toBe(failure); }); + async function ownedAdapterRun() { + const { company, agent } = await fixture(); + const [run] = await db.insert(heartbeatRuns).values({ companyId: company.id, agentId: agent.id, status: "running", runtimeMode: "legacy", runtimeModeResolvedAt: new Date() }).returning(); + const control = createAdapterExecutionControl(); + adapterExecutionControls.set(run.id, control); + return { run, control }; + } + + it("answers an owned adapter Stop that is still settling with a pending conflict", async () => { + const { run, control } = await ownedAdapterRun(); + const deps = callbacks(db); + deps.activeRunExecutions.add(run.id); + try { + let waitStartedAt = 0; + vi.mocked(waitForAdapterStop).mockImplementationOnce(async () => { + waitStartedAt = Date.now(); + throw new AdapterStopTimeoutError(60_000, { runId: run.id }); + }); + const requestedAt = Date.now(); + const error = await createHeartbeatRunControl(db, deps).cancelRunInternal(run.id).catch((err: unknown) => err); + expect(error).toBeInstanceOf(HttpError); + expect(error).toMatchObject({ + status: 409, + message: "Execution is still stopping; termination has not been verified.", + details: { code: "adapter_stop_pending", runId: run.id }, + }); + expect(control.controller.signal.aborted).toBe(true); + // Only a Stop that never settles is reported, from the same execution. + expect(reportAdapterStopIfUnsettled).toHaveBeenCalledWith(control.settled, captureException, + expect.objectContaining({ runId: run.id, adapterType: "codex_local", runtimeMode: "legacy", abortRequested: true }), control, + { stopRequestedAt: expect.any(Number) }); + // Its report bound runs from the Stop request, before the response wait. + const { stopRequestedAt } = vi.mocked(reportAdapterStopIfUnsettled).mock.calls.at(-1)![4]!; + expect(stopRequestedAt).toBeGreaterThanOrEqual(requestedAt); + expect(stopRequestedAt).toBeLessThanOrEqual(waitStartedAt); + // A pending Stop is not a cancellation receipt. + expect(deps.setRunStatusFromLive).not.toHaveBeenCalled(); + expect(deps.finalizeAgentStatus).not.toHaveBeenCalled(); + expect((await deps.getRun(run.id))?.status).toBe("running"); + expect(deps.failedProcessRunCancellations.has(run.id)).toBe(false); + } finally { + control.finish(); + adapterExecutionControls.delete(run.id); + } + }); + + it("keeps other owned adapter Stop failures unchanged", async () => { + const { run, control } = await ownedAdapterRun(); + const deps = callbacks(db); + vi.mocked(reportAdapterStopIfUnsettled).mockClear(); + try { + const failure = new Error("owner wait failed"); + vi.mocked(waitForAdapterStop).mockRejectedValueOnce(failure); + await expect(createHeartbeatRunControl(db, deps).cancelRunInternal(run.id)).rejects.toBe(failure); + expect(reportAdapterStopIfUnsettled).not.toHaveBeenCalled(); + } finally { + control.finish(); + adapterExecutionControls.delete(run.id); + } + }); + it("deduplicates agent batches and cancels only unclaimed pending wakes", async () => { const { company, agent } = await fixture(); const [otherAgent] = await db.insert(agents).values({ companyId: company.id, name: "Other agent" }).returning(); diff --git a/server/src/services/heartbeat/run-control.ts b/server/src/services/heartbeat/run-control.ts index f744f35d147..f4b0529ddc9 100644 --- a/server/src/services/heartbeat/run-control.ts +++ b/server/src/services/heartbeat/run-control.ts @@ -44,8 +44,11 @@ import { import { adapterExecutionControls, captureAdapterStopOwnership, + reportAdapterStopIfUnsettled, waitForAdapterStop, } from "../adapter-execution-control.js"; +import { AdapterStopTimeoutError } from "../adapter-stop-timeout.js"; +import { captureException } from "../../sentry.js"; import { randomUUID } from "node:crypto"; import { and, @@ -999,6 +1002,7 @@ export function createHeartbeatRunControl(db: Db, dependencies: HeartbeatRunCont processCancellationSettlement, ); } + const stopRequestedAt = Date.now(); const cancellation = await (async () => { try { if (control) { @@ -1059,12 +1063,24 @@ export function createHeartbeatRunControl(db: Db, dependencies: HeartbeatRunCont } if (control) { - await waitForAdapterStop(control.settled, undefined, { + const stopDiagnostics = { runId: run.id, adapterType: agent?.adapterType, runtimeMode: run.runtimeMode, abortRequested: control.controller.signal.aborted, - }, control); + }; + try { + await waitForAdapterStop(control.settled, undefined, stopDiagnostics, control); + } catch (error) { + if (!(error instanceof AdapterStopTimeoutError)) throw error; + // The owned adapter is still settling (for example copying the + // workspace back) and keeps its Stop proof requirements. Answer + // this request honestly; report only a Stop that never settles. + reportAdapterStopIfUnsettled(control.settled, captureException, stopDiagnostics, control, { + stopRequestedAt, + }); + throw conflict(error.message, { code: "adapter_stop_pending", runId: run.id }); + } const stopped = await getRun(run.id); if (stopped && isHeartbeatRunTerminalStatus(stopped.status)) { if ( From 64bd6e9e07210240196f6b9969d25a23aa98fe0e Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 17:46:25 -0500 Subject: [PATCH 10/37] refactor(chat): extract delivery, reaction, and setup boundaries (#16024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip manages AI agents and their work. > - Chat channels connect that work to provider conversations. > - One shared service contains callback, delivery, reaction, and setup logic. > - These responsibilities have distinct contracts and existing public-path tests. > - This PR moves ten cohesive responsibilities into focused modules. > - The service retains shared runtime ownership and scheduling. > - Provider behavior is easier to locate and review. ## Linked Issues or Issue Description Refs #15995, #16026, #16027, #16030, #16031, #16035, #16037, #16038, #16041, #16043. This PR consolidates the reviewed extraction stack. The plan is `doc/plans/2026-10-12-chat-channel-delivery-boundaries-refactor.md`. ## What Changed - Move Slack callback verification into the Slack folder. - Move Discord Gateway events and root-thread admission/recovery into the Discord folder. - Extract durable receipt reactions and inbound reaction admission/replay. - Extract inbound attachment ingestion and outbound integrity-checked hydration. Preserve the public hydration export. - Extract durable provider effects. Keep Telegram callback notices in the Telegram folder. - Extract conversation/activity reads and setup qualification. Keep Slack and Teams projections in their channel folders. - Preserve the merged managed Slack setup behavior, shared workers, credential leases, and transaction boundaries. - Reduce `chat-channels.ts` from 26,493 lines / 1,048,769 bytes to 21,460 lines / 862,284 bytes. This removes 5,033 lines from the service. ## Verification - Each extraction passed its selected existing tests before and after through public service paths. - On updated master, all 55 selected Slack cases passed before extraction. All 194 selected chat/managed-Slack cases passed on the final stack tip. - `pnpm -r typecheck`, `pnpm build`, module-boundary checks, and whitespace checks passed on that tip. - The normalized syntax audit verifies 69 moved declarations and all retained statements, including their relative order, against updated master. - The eight earlier stack merges retain the exact validated repository tree. The combined root equals the validated tip merged with current master. All chat-channel files remain byte-identical to the validated tip. Current combined-root CI and review are pending. - Full local verification remains unverified. The unchanged `workspace-git-snapshot-streaming.test.ts:56` stress test timed out at 300,000 ms in a clean run and again in isolation. The full run was stopped after the reproduced failure. No timeout or assertion was weakened. - These checks use assembled services, real local databases, and provider fixtures. They do not prove live third-party execution. Reviewers can run the public-path suites: ```sh pnpm exec vitest run server/src/__tests__/chat-channels.integration.test.ts server/src/services/chat-outbound-attachment-hydration.test.ts server/src/__tests__/slack-development-manager.test.ts ``` ## Risks - Factory wiring must preserve live values, shared state, and initialization order. Identity-link construction retains deferred access to the provider-effects callback. - Credential fences, authorization transactions, and delivery settlement must keep their original ordering. Syntax parity and public-path integration cases cover these boundaries. - Full local verification has the recorded Git-streaming timeout. The combined PR must pass master’s required CI before merging into master. ## Model Used OpenAI Codex, GPT-6 family, with reasoning and repository tools. The exact runtime model ID and context-window size are not exposed in this session. No separate agent was used. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- ...at-channel-delivery-boundaries-refactor.md | 100 + server/src/services/chat-channels.ts | 5107 +---------------- server/src/services/chat-channels/activity.ts | 616 ++ .../chat-channels/discord/gateway-events.ts | 253 + .../chat-channels/discord/messages.ts | 9 + .../chat-channels/discord/root-threads.ts | 570 ++ .../chat-channels/inbound-attachments.ts | 674 +++ .../file-transfer-projection.ts | 18 + .../chat-channels/outbound-attachments.ts | 294 + .../chat-channels/provider-effects.ts | 957 +++ .../src/services/chat-channels/reactions.ts | 891 +++ .../chat-channels/receipt-reactions.ts | 586 ++ .../services/chat-channels/runtime-types.ts | 16 +- .../services/chat-channels/setup-probes.ts | 353 ++ .../slack/callback-verification.ts | 300 + .../services/chat-channels/slack/messages.ts | 46 + .../chat-channels/slack/task-start.ts | 18 + .../telegram/callback-notices.ts | 128 + .../chat-channels/telegram/messages.ts | 12 + 19 files changed, 5877 insertions(+), 5071 deletions(-) create mode 100644 doc/plans/2026-10-12-chat-channel-delivery-boundaries-refactor.md create mode 100644 server/src/services/chat-channels/activity.ts create mode 100644 server/src/services/chat-channels/discord/gateway-events.ts create mode 100644 server/src/services/chat-channels/discord/root-threads.ts create mode 100644 server/src/services/chat-channels/inbound-attachments.ts create mode 100644 server/src/services/chat-channels/microsoft-teams/file-transfer-projection.ts create mode 100644 server/src/services/chat-channels/outbound-attachments.ts create mode 100644 server/src/services/chat-channels/provider-effects.ts create mode 100644 server/src/services/chat-channels/reactions.ts create mode 100644 server/src/services/chat-channels/receipt-reactions.ts create mode 100644 server/src/services/chat-channels/setup-probes.ts create mode 100644 server/src/services/chat-channels/slack/callback-verification.ts create mode 100644 server/src/services/chat-channels/telegram/callback-notices.ts diff --git a/doc/plans/2026-10-12-chat-channel-delivery-boundaries-refactor.md b/doc/plans/2026-10-12-chat-channel-delivery-boundaries-refactor.md new file mode 100644 index 00000000000..01a3fb22ef9 --- /dev/null +++ b/doc/plans/2026-10-12-chat-channel-delivery-boundaries-refactor.md @@ -0,0 +1,100 @@ +# Chat channel callback and delivery boundaries, batch four + +Base: 4b766b872993045a2e787404f0475503683e5ded. Batch three (#15995, #15996, #15999, #16001, #16002) +is merged. The service starts at 26,461 lines / 1,046,711 bytes. +This batch may create up to ten stacked PRs. Merging the new batch is separate work. + +| Order / boundary | Destination | Approx. moved / net lines | Contract and state | Risks and existing verification | +| --- | --- | --- | --- | --- | +| 1. Slack callback verification | `slack/callback-verification.ts` | 250 / 230 | Pure signature/workspace/URL inspection plus signed URL verification; explicit vault lookup, URL getter, scheduling and registration callbacks | Preserve signing-secret-only setup, original request, TLS/proxy evidence and workspace checks. Signed verification and callback health cases before/after. | +| 2. Discord Gateway events | `discord/gateway-events.ts` | 200 / 185 | Runtime fence and lease-owner predicates, endpoint lookup and lifecycle callback; original per-runtime event queue and sequence | Preserve stale-owner fencing, event order, transient guild recovery and terminal revocation. Gateway lifecycle cases before/after. | +| 3. Discord root threads | `discord/root-threads.ts` | 500 / 460 | Credential lease, current principal authority, runtime, message admission and conversation drain callbacks | Preserve uncertain thread creation, restart reconciliation, no duplicate starter and pause/credential serialization. Root-thread public-service cases. | +| 4. Receipt reactions | `receipt-reactions.ts` and Slack error classifier beside Slack messages | 545 / 500 | Durable reaction claim/transport/settlement with runtime and credential fences; no scheduling owner moves | Preserve retries, legacy GitHub receipts, terminal-removal supersession and same-attempt settlement. Receipt recovery cases before/after. | +| 5. Inbound reactions | `reactions.ts` | 800 / 740 | Conversation lookup/locks, reaction admission and deferred replay together; current principal/runtime callbacks | Preserve exact event dedupe, completed generation links, bounded replay age and no task wakeup. Reaction race/restart/authorization cases. | +| 6. Inbound attachments | `inbound-attachments.ts` | 600 / 550 | Existing storage and issue service, retained source authorization and live options; return same attachment results | Preserve size limits, source authority around I/O, omissions and text delivery. Provider attachment/restart/stale-authority cases. | +| 7. Outbound attachments | `outbound-attachments.ts`; provider Markdown wrappers beside provider messages | 245 / 220 | Metadata validation and bounded integrity-checked hydration; existing storage object. Keep public hydration export compatible | Preserve stream cleanup, deadlines, byte/digest checks and retry classification. Hydration and publication attachment cases. | +| 8. Provider effects | `provider-effects.ts` | 950 / 850 | Durable reply claim, authorization, transport and settlement; endpoint/runtime/credential/principal callbacks | Preserve uncertain-delivery quarantine, credential fence and transaction ordering. Native callback, task-control and reply recovery cases. Put Telegram-only authority under `telegram/` if its contract is narrow. | +| 9. Conversation and activity reads | `activity.ts` | 540 / 520 | Endpoint lookup and read-only Teams conflict projection; return existing API models | Preserve company/endpoint joins, mixed-source cursor ties, replay eligibility and read-only GETs. Activity, file receipt projection and conversation-state cases. | +| 10. Setup probes | `setup-probes.ts` | 280 / 250 | Credential lease, current identity/principal authority, provider fetch, endpoint projection and live activation hook | Preserve real-message requirement, generation and linked-user activation. Setup-test, reconnect and authorization cases. | + +Each slice must earn its place after reading its complete path and callers. +Reassess before implementation; stop short of ten if the remaining contracts are +not cohesive. Keep provider-specific code in channel folders and common durable +workflows in the shared chat-channels directory. + +Receiver lease ownership stays shared: the existing Discord-named owner is also +used by Photon. Shared lifecycle/application admission, queue-drain ownership, +worker scheduling, and the largest action/message handlers remain in the service. +Do not hide their dependencies in a mutable context object. Preserve live option +hooks, runtime-map identity, partial-failure cleanup, and initialization order. + +Run relevant existing public-service tests before and after each mechanical move. +Compare moved declarations and remaining statements with normalized syntax, then +run server types, module boundaries and whitespace checks. Finish local verification and a fresh +5/5 review before opening the next draft PR. GitHub runner-selection jobs remained +queued without steps for over 25 minutes in this batch. To continue the authorized +stack, publish drafts in review order while tracking CI as pending; CI remains a +merge gate. Run cumulative repository gates at handoff. +Retain failures and skipped/unrun coverage even when an isolated retry passes. +Use fixture-backed assembled service/database tests; do not claim live provider +execution. Remap LOC/bytes and the remaining orchestration after the batch. + +## Slice 1: Slack callback verification + +- Service: 26,461 lines / 1,046,711 bytes → 26,201 lines / 1,035,260 bytes. +- Destination: 299 lines / 13,038 bytes. Eight declarations move without body + changes. Three existing setup types move unchanged into `runtime-types.ts`. +- The verification factory initializes after Slack registration. It receives the + same registration object, credential resolver, live URL getter and scheduler. + Signature/workspace/URL helpers remain callable by shared webhook orchestration. +- Nine public-service cases passed before and after on the updated master base. + Server typecheck, module boundaries, whitespace and normalized syntax parity + passed. Coverage includes signed URL verification, account notification, + workspace rejection, callback surfaces, TLS termination and Cloud host evidence. +- Next: Discord Gateway event sequencing and health transitions. Keep shared + receiver lease ownership and runtime-map lifetime in the service. + +## Batch outcome + +All ten candidates produced cohesive boundaries. Shared receiver ownership, queue +management and worker scheduling remain in the service. The only intentional +control-wiring change is deferred access to `stageProviderEffect` from identity +link construction; typecheck and integration tests caught the initial early read. + +| Slice | Service lines after | Primary module lines | Before/after cases | +| --- | ---: | ---: | ---: | +| 1. Slack signature, workspace and callback url verification | 26,233 | 300 | 9 | +| 2. Discord gateway event sequencing and endpoint health transitions | 26,029 | 253 | 7 | +| 3. Discord root-thread admission, provider creation and durable recovery | 25,527 | 570 | 4 | +| 4. Durable receipt reaction claims, transport, retries and settlement | 24,975 | 586 | 7 | +| 5. Inbound reaction conversation lookup, admission and deferred replay | 24,160 | 891 | 21 | +| 6. Inbound attachment authorization, bounded ingestion, registration and omissions | 23,565 | 674 | 39 | +| 7. Outbound attachment metadata validation and bounded integrity-checked hydration | 23,298 | 294 | 22 | +| 8. Durable provider reply staging, authorization, transport and recovery | 22,331 | 957 | 24 | +| 9. Conversation summaries and paginated activity projections | 21,741 | 616 | 16 | +| 10. Setup message detection and credential-fenced round-trip qualification | 21,460 | 353 | 7 | + +After rebasing on master `8e7b5db822`, the service shrank from +**26,493 lines / 1,048,769 bytes** to **21,460 lines / +862,284 bytes** (5,033 fewer lines, about 19%). Normalized syntax comparison +checks all 69 moved declarations/types and every remaining original statement. +Server types, targeted tests, module boundaries and whitespace checks are the +per-slice gates. Cumulative repository gates and remote reviews are recorded in +the PR descriptions as they complete. + +Orchestration measurement: the service function has 150 nested functions +(19,399 lines) and 67 state/wiring statements (459 lines), versus 191 functions +(23,829 lines) and 54 statements (425 lines) at the start. Imports take 400 lines. +The remaining large paths are `processMessage` (2,166), `handleAction` (1,074), +`configureWithCredentialLease` (868), `processSelectedPublication` (729), +`handleModalSubmit` (708), `handleWebhook` (685), and lifecycle processing (657). + +A future pass should inspect publication admission/transport/settlement as one +workflow, then inbound delivery authorization and drain ownership. Those cuts +need explicit ownership contracts; avoid separating individual checks from their +transactions merely to reduce the remaining line count. + +The rebase preserves the merged managed Slack setup flow, including its shared +`isProvisionedSlackSetup` predicate in the extracted callback verification +module. A fresh syntax audit compares all moved declarations and retained +statement ordering with this updated master. diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index e1077280799..6a302b30fdc 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -1,6 +1,22 @@ import { slackTestMessageService } from "./connectors/slack/test-message.js"; import type { SlackSetupOptions } from "./connectors/slack/setup/registration.js"; import { isProvisionedSlackSetup } from "@paperclipai/shared"; +import { createSetupProbes } from "./chat-channels/setup-probes.js"; +import { createChatActivity, publicationSummary } from "./chat-channels/activity.js"; +import { fileTransferProjectionColumns } from "./chat-channels/microsoft-teams/file-transfer-projection.js"; +import { createProviderEffects, providerEffectPayload, type ProviderEffectTarget, type ProviderEffectPayload } from "./chat-channels/provider-effects.js"; +import { createTelegramCallbackNoticeAuthority } from "./chat-channels/telegram/callback-notices.js"; +import { createOutboundAttachments, isOutboundAttachmentHydrationError, isOutboundAttachmentValidationError } from "./chat-channels/outbound-attachments.js"; +export { hydrateOutboundAttachment } from "./chat-channels/outbound-attachments.js"; +import { discordMarkdownAttachment } from "./chat-channels/discord/messages.js"; +import { telegramMarkdownAttachment } from "./chat-channels/telegram/messages.js"; +import { createInboundAttachments } from "./chat-channels/inbound-attachments.js"; +import { createInboundReactions } from "./chat-channels/reactions.js"; +import { slackThreadChannelId } from "./chat-channels/slack/messages.js"; +import { createReceiptReactions } from "./chat-channels/receipt-reactions.js"; +import { createDiscordRootThreads } from "./chat-channels/discord/root-threads.js"; +import { createDiscordGatewayEvents } from "./chat-channels/discord/gateway-events.js"; +import { createSlackCallbackVerification, slackRequestSignatureIsValid, slackRequestWorkspaceId, canonicalCallbackUrl, slackCallbackMatchesPublicUrl, inspectSlackCallback } from "./chat-channels/slack/callback-verification.js"; import { createTeamsFileAuthority } from "./chat-channels/microsoft-teams/file-transfer-authority.js"; import { createPhotonEvents } from "./chat-channels/imessage-photon/events.js"; import { createTelegramPublicationDrafts } from "./chat-channels/telegram/publication-drafts.js"; @@ -8,10 +24,10 @@ import { createSlackFileReceipts } from "./chat-channels/slack/file-receipts.js" import { createSlackSessionStatus } from "./chat-channels/slack/session-status.js"; import { createDiscordCommands } from "./chat-channels/discord/commands.js"; import { createSlackTaskStart, slackSlashTaskRecoveryPayload, slackTaskStarterThreadId } from "./chat-channels/slack/task-start.js"; -import { createGitHubWebhookIngress, GITHUB_RECOVERY_STATE_KEY, type GitHubIngressStageResult } from "./chat-channels/github/webhook-ingress.js"; +import { createGitHubWebhookIngress, type GitHubIngressStageResult } from "./chat-channels/github/webhook-ingress.js"; import { createSlackSessionStop } from "./chat-channels/slack/session-stop.js"; import { createTelegramMaintenance } from "./chat-channels/telegram/maintenance.js"; -import type { EndpointRow, DbTransaction, DbOrTransaction, RuntimeContext, LifecycleRuntimeFence, InboundRuntimeContext } from "./chat-channels/runtime-types.js"; +import type { EndpointRow, DbTransaction, DbOrTransaction, RuntimeContext, LifecycleRuntimeFence, InboundRuntimeContext, SlackCallbackSurface, InternalSetupState } from "./chat-channels/runtime-types.js"; import { createChatFailedRunRetry } from "./chat-channels/failed-run-retry.js"; import { createChatPublicationReplacement } from "./chat-channels/publication-replacement.js"; import { createChatIdentityLinks } from "./chat-channels/identity-links.js"; @@ -51,7 +67,7 @@ import { type GitHubLifecycleEvent, githubLifecycleEventFromRequest, } from "./chat-channels/github/messages.js"; -import { dueChatAction, nextChatActionAt, SLACK_COMMAND_POST_STALE_MS, SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS, PROVIDER_EFFECT_STALE_MS } from "./chat-action-work.js"; +import { dueChatAction, nextChatActionAt, SLACK_COMMAND_POST_STALE_MS, SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS } from "./chat-action-work.js"; import type { ScheduledChatAction } from "./chat-work-notifications.js"; import { notifyChatDeliveryWork, notifyChatPublicationWork, notifyChatEndpointWork, notifyChatActionWork, notifyChatVerificationWork } from "./chat-work-notifications.js"; import { syncSpekoVoiceTools } from "./voice/speko-agent-tools.js"; @@ -64,9 +80,7 @@ import type { ChatEndpointRuntime } from "./voice/voice-runtime.js"; import { parseMarkdown } from "chat"; import { githubPublicAttachmentsFromMessage } from "./chat-github-attachments.js"; import { - receiptReactionPayload, stageReceiptReactionRemovals, - type ReceiptReactionPayload, } from "./chat-receipt-reactions.js"; import { fastResponseService, enqueueFastResponse, fastResponseSourceCurrent, fastResponseTurnQueued } from "./fast-responses.js"; import { fastResponseRequests } from "@paperclipai/db"; @@ -89,7 +103,7 @@ import { registerSlackTaskAuthority, slackRunOrigin } from "./connectors/slack-a import { buildChatCommunicationGuidance } from "./chat-communication-guidance.js"; function githubPolicyRecord(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) ? value as Record : {}; } -import { githubChatManagementService } from "./chat-github-management.js"; + import { githubReviewCheckService } from "./chat-github-checks.js"; import { githubAutomaticReviewEvent, @@ -112,12 +126,12 @@ import { githubAppJwt } from "./chat-github-client.js"; import { resumeExternalConversation } from "./slack-conversation-state.js"; import { settleExternalConversation } from "./slack-conversation-lifecycle.js"; import { runtimeCanonicalOrigin } from "./cloud-runtime-identity.js"; -import { takePhotonCompanion } from "./photon/attachments.js"; + import { writePhotonCheckpoint } from "./photon/receiver.js"; import { PhotonState } from "./photon/state.js"; -import { HEIF_CONTENT_TYPES, photonHeifPreview, validatePhotonImage } from "./photon/media.js"; -import { projectSafeChatPublicationText } from "./chat-publication-projection.js"; + + import { nativePhotonInteraction, publishPhotonPrompt, type PhotonPromptReceipt, type PhotonInteractionBinding } from "./photon/interactions.js"; @@ -139,7 +153,6 @@ import { desc, eq, gt, - gte, inArray, isNotNull, isNull, @@ -205,7 +218,6 @@ import type { ChatEndpointSetupState, ChatProvider, ChatPublicationBatchStatus, - ChatPublicationSummary, ConfigureChatEndpointInput, CreateChatEndpointInput, ExternalChannelBindingSummary, @@ -222,10 +234,8 @@ import { } from "@paperclipai/shared"; import { formatAttachmentSize, - isAllowedContentType, MAX_ATTACHMENT_BYTES, normalizeContentType, - normalizeUploadAttachmentContentType, } from "../attachment-types.js"; import { isUniqueViolation } from "../db-errors.js"; import { @@ -239,24 +249,17 @@ import { import { projectTeamsFilePublication } from "./chat-teams-file-publication.js"; import type { TeamsFileConsentEvent } from "./chat-teams-file-consent.js"; import { - chatFileTransferResolutionActions, projectChatFileTransfer, projectChatPublicationBatch, } from "./chat-publication-batches.js"; import { telegramAttachmentForUpload } from "./chat-telegram-photo.js"; import { - parseTelegramCallbackReceipt, readTelegramCallbackProvenance, telegramCallbackThreadId, TELEGRAM_PRIVATE_ACTION_UNAVAILABLE, - type TelegramCallbackReceipt, } from "./chat-telegram-ephemeral.js"; -import { - hasTelegramMediaProvenance, - identifyTelegramMedia, - telegramMediaNeedsIdentification, -} from "./chat-telegram-media-intake.js"; + import { normalizeTelegramRichMessage } from "./chat-telegram-rich-intake.js"; import { isTelegramDraftStopped, @@ -265,10 +268,7 @@ import { import { parseChatWebhookPublicBaseUrl } from "../chat-webhook-public-url.js"; -import { - parseGitHubReceiptIdentity, - type GitHubReceiptIdentity, -} from "./chat-github-receipt-reactions.js"; + import { badRequest, HttpError, @@ -281,14 +281,12 @@ import { logger } from "../middleware/logger.js"; import { redactSensitiveText } from "../redaction.js"; import type { StorageService } from "../storage/types.js"; import { - DiscordAdapterCompatibilityError, type ChatSdkCallbackEvent, type ChatSdkMessageCallbackEvent, type ChatSdkMessageUpdatedCallbackEvent, type ChatSdkProvider, type ChatSdkRuntime, type DiscordGatewayCallbackEvent, - type DiscordRootMentionAdmissionEvent, type ResolvedChatSdkProviderConfig, type SlackFileUploadAcceptedReceipt, } from "./chat-sdk-runtime.js"; @@ -322,10 +320,7 @@ import { requestGitHubAppWebhookRedelivery, } from "./chat-github-webhook-config.js"; import { - GITHUB_ATTACHMENT_BATCH_TIMEOUT_MS, - GitHubAttachmentUnavailableError, githubAttachmentLimitOmissions, - prepareGitHubPublicAttachment, restoreGitHubAttachmentLimitOmissions, } from "./chat-github-attachments.js"; import { getExternalChannelBindingSummary } from "./chat-channel-binding.js"; @@ -383,9 +378,7 @@ import { normalizeMicrosoftTeamsCredentialIds, } from "./chat-teams-credentials.js"; import { - isTeamsInlineImageContentType, prepareTeamsInlineImage, - TEAMS_INLINE_IMAGE_MAX_BYTES, } from "./chat-teams-inline-images.js"; import { shouldStreamSafePublicationText, @@ -419,44 +412,11 @@ import type { MessageDeletedEvent, ModalResponse, ModalSubmitEvent, - ReactionEvent, SlashCommandEvent, Thread, } from "chat"; import { Actions, Button, Card, CardText, LinkButton } from "chat"; -// Only these closed fields may reach Activity or a delivery-batch response. -// The resolution predicate yields one boolean, never the private envelope. -const fileTransferProjectionColumns = { - publicationId: chatTeamsFileTransfers.publicationId, - phase: chatTeamsFileTransfers.phase, - version: chatTeamsFileTransfers.version, - filename: chatTeamsFileTransfers.filename, - expiresAt: chatTeamsFileTransfers.expiresAt, - consentMessageId: chatTeamsFileTransfers.consentMessageId, - responseActivityId: chatTeamsFileTransfers.responseActivityId, - fileInfoMessageId: chatTeamsFileTransfers.fileInfoMessageId, - operatorConfirmed: sql` - coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,schema}', '') = 'paperclip.teams.file-resolution.v1' - and coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,action}', '') = 'mark_delivered' - and coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,fromPhase}', '') = 'file_info_unknown' - `, -}; - -function publicationSummary( - row: typeof chatPublications.$inferSelect, -): ChatPublicationSummary { - return { - id: row.id, - state: row.state, - providerUrl: row.providerUrl, - attempts: row.attempts, - redactedError: row.redactedError, - nextAttemptAt: row.nextAttemptAt?.toISOString() ?? null, - publishedAt: row.publishedAt?.toISOString() ?? null, - }; -} - const PROVIDER_LABELS: Record = { speko: "Speko", "imessage-photon": "iMessage Photon", @@ -494,207 +454,6 @@ function publicationPrecedes( ); } -function slackRequestSignatureIsValid( - request: Request, - body: string, - signingSecret: string, -): boolean { - const timestamp = request.headers.get("x-slack-request-timestamp"); - const signature = request.headers.get("x-slack-signature"); - if (!timestamp || !signature) return false; - const timestampSeconds = Number(timestamp); - if ( - !Number.isFinite(timestampSeconds) || - Math.abs(Math.floor(Date.now() / 1000) - timestampSeconds) > 300 - ) - return false; - const expected = `v0=${createHmac("sha256", signingSecret).update(`v0:${timestamp}:${body}`).digest("hex")}`; - try { - return timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); - } catch { - return false; - } -} - -function slackRequestWorkspaceId( - body: string, - contentType: string, -): string | null { - let payload: unknown; - try { - if (contentType.includes("application/json")) { - payload = JSON.parse(body); - } else { - const form = new URLSearchParams(body); - const interactivePayload = form.get("payload"); - payload = interactivePayload - ? JSON.parse(interactivePayload) - : Object.fromEntries(form.entries()); - } - } catch { - return null; - } - if (!payload || typeof payload !== "object") return null; - const record = payload as Record; - const nestedId = (value: unknown): string | null => { - if (!value || typeof value !== "object") return null; - const id = (value as Record).id; - return typeof id === "string" && id.length > 0 ? id : null; - }; - const stringField = (key: string): string | null => { - const value = record[key]; - return typeof value === "string" && value.length > 0 ? value : null; - }; - const authorization = Array.isArray(record.authorizations) - ? record.authorizations.find( - (value): value is Record => - Boolean(value) && typeof value === "object", - ) - : null; - const authorizationField = (key: string): string | null => { - const value = authorization?.[key]; - return typeof value === "string" && value.length > 0 ? value : null; - }; - - // A Grid event may carry both an enterprise id and the concrete workspace - // id. The bot identity is verified against auth.test's team_id, so prefer a - // team id and use the enterprise id only when Slack omitted team context. - return ( - stringField("team_id") ?? - nestedId(record.team) ?? - authorizationField("team_id") ?? - stringField("enterprise_id") ?? - nestedId(record.enterprise) ?? - authorizationField("enterprise_id") - ); -} - -function canonicalCallbackUrl(value: string): string | null { - try { - const url = new URL(value); - if (url.protocol !== "https:" && url.protocol !== "http:") return null; - url.hostname = url.hostname.replace(/\.$/, ""); - url.username = ""; - url.password = ""; - url.search = ""; - url.hash = ""; - return url.toString(); - } catch { - return null; - } -} - -// TLS terminates at a reverse proxy in many self-hosted deployments. The -// transport scheme is not evidence of URL drift; authority and path still are. -// This is diagnostic only: it does not trust forwarded headers or change auth. -function slackCallbackMatchesPublicUrl(observed: string, current: string): boolean { - const canonical = canonicalCallbackUrl(observed); - if (canonical === current) return true; - if (!canonical) return false; - const observedUrl = new URL(canonical); - const currentUrl = new URL(current); - return observedUrl.protocol === "http:" - && currentUrl.protocol === "https:" - && observedUrl.host === currentUrl.host - && observedUrl.pathname === currentUrl.pathname; -} - -type SlackCallbackInspection = { - surface: SlackCallbackSurface; - url: string; - isUrlVerification: boolean; -}; - -// Cloud records the public request host in dedicated headers so provider edges -// cannot replace it with their upstream host. Only use callback-health evidence on a -// claimed Cloud instance, after provider authentication succeeded. It must not -// change the Request passed to the adapter, signature verification, routing, -// board identity, or the configured URL used to generate callbacks. -function slackCallbackObservationUrl(request: Request): string | null { - const directUrl = canonicalCallbackUrl(request.url); - if (!directUrl || !runtimeCanonicalOrigin()) return directUrl; - const hasCloudEvidence = request.headers.has("x-paperclip-cloud-forwarded-host"); - const host = request.headers.get(hasCloudEvidence ? "x-paperclip-cloud-forwarded-host" : "x-forwarded-host")?.trim(); - const protocol = request.headers.get(hasCloudEvidence ? "x-paperclip-cloud-forwarded-proto" : "x-forwarded-proto")?.trim(); - if (!host || /[\s/@\\?#,]/.test(host) || (protocol !== "https" && protocol !== "http")) return directUrl; - try { - const origin = new URL(`${protocol}://${host}`); - // Accept one authority only, never credentials, paths, queries, or lists. - if (!origin.host || origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) return directUrl; - const observed = new URL(directUrl); - observed.protocol = origin.protocol; - observed.host = origin.host; - observed.port = origin.port; - return canonicalCallbackUrl(observed.toString()); - } catch { - return directUrl; - } -} - -async function inspectSlackCallback( - request: Request, -): Promise { - const url = slackCallbackObservationUrl(request); - if (!url) return null; - const contentType = request.headers.get("content-type")?.toLowerCase() ?? ""; - let body: string; - try { - body = await request.text(); - } catch { - return null; - } - if (contentType.includes("application/json")) { - try { - const payload = JSON.parse(body) as { - type?: unknown; - challenge?: unknown; - }; - const isUrlVerification = - payload.type === "url_verification" && - typeof payload.challenge === "string" && - payload.challenge.length > 0; - if (isUrlVerification || payload.type === "event_callback") { - return { - surface: "events", - url, - isUrlVerification, - }; - } - } catch { - return null; - } - return null; - } - - const form = new URLSearchParams(body); - if (form.get("command")) { - return { surface: "slashCommands", url, isUrlVerification: false }; - } - const interactivePayload = form.get("payload"); - if (!interactivePayload) return null; - try { - const payload = JSON.parse(interactivePayload) as { type?: unknown }; - if ( - typeof payload.type === "string" && - [ - "block_actions", - "block_suggestion", - "dialog_submission", - "message_action", - "shortcut", - "view_closed", - "view_submission", - "workflow_step_edit", - ].includes(payload.type) - ) { - return { surface: "interactivity", url, isUrlVerification: false }; - } - } catch { - return null; - } - return null; -} - const CAPABILITIES: Record = { speko: { threads: false, directMessages: true, nativeStreaming: false, messageEdits: false, messageDeletes: false, reactions: false, files: false, cards: false, actions: false, modals: false, slashCommands: false, ephemeralMessages: false, proactiveDirectMessages: false }, "imessage-photon": { threads: false, directMessages: true, nativeStreaming: false, messageEdits: true, messageDeletes: false, reactions: false, files: true, cards: false, actions: true, modals: false, slashCommands: false, ephemeralMessages: false, proactiveDirectMessages: false }, @@ -841,13 +600,6 @@ const SUPPLIED_CREDENTIAL_KEYS: Record = { const MAX_ERROR_TEXT = 2_000; const DELIVERY_PROCESSING_STALE_MS = 60_000; -// Reactions can beat the transaction that persists a just-sent provider -// message link. Keep that narrow gap durable, but never retain an unbound -// reaction indefinitely or let it enter the ordinary inbound-message FIFO. -const REACTION_LINK_RETRY_DELAY_MS = 250; -const REACTION_LINK_MAX_DELAY_MS = 5_000; -const REACTION_LINK_MAX_ATTEMPTS = 20; -const REACTION_LINK_MAX_AGE_MS = 2 * 60_000; // A Discord root mention is durably staged before the adapter creates its // provider thread. Give the bounded provider retry loop ample time to finish; // if the process disappears, the delivery worker verifies that thread over @@ -887,17 +639,6 @@ const INGRESS_REORDER_WINDOW_MS: Partial> = { telegram: 750, }; type ResourceRow = typeof chatEndpointResources.$inferSelect; -type SlackCallbackSurface = keyof ChatEndpointCallbackSurfaces; -type SlackCallbackObservation = { url: string; observedAt: string }; -type InternalSetupState = ChatEndpointSetupState & { - photonIntakeAfter?: string; - runtimeGeneration?: number; - /** Binds pre-install URL verification to the signing secret later configured. */ - slackVerificationSigningFingerprint?: string; - slackCallbackSurfaces?: Partial< - Record - >; -}; type VerifiedProviderIdentity = { githubOwnerType?: "personal" | "organization"; githubOwnerLogin?: string; @@ -1054,130 +795,6 @@ type LiveInboundMessage = { thread: Thread; trigger: ChatSdkMessageCallbackEvent["trigger"]; }; -type ProviderEffectTarget = { - post: Thread["post"]; - postEphemeral?: Thread["postEphemeral"]; -}; -type ProviderEffectPayload = { - version: 1; - effect: "thread_message" | "ephemeral_message" | "telegram_callback_notice"; - telegramCallback?: TelegramCallbackReceipt; - telegramAuthorizationSha256?: string; - authorizationMode?: "principal" | "safe_notice"; - threadId: string; - userId?: string; - text: string; - fallbackText?: string; - settleDelivery: boolean; - completeConversationId?: string; - resourceId?: string; - runtimeGeneration: number; - credentialFingerprint: string; -}; - -function isSlackReceiptReactionAlreadyApplied(error: unknown): boolean { - const pending: Array<{ depth: number; value: unknown }> = [ - { depth: 0, value: error }, - ]; - const seen = new Set(); - while (pending.length > 0) { - const current = pending.shift(); - if ( - !current || - current.depth > 4 || - !current.value || - typeof current.value !== "object" || - seen.has(current.value) - ) { - continue; - } - seen.add(current.value); - const record = current.value as { - cause?: unknown; - code?: unknown; - data?: { error?: unknown }; - original?: unknown; - originalError?: unknown; - }; - if ( - record.code === "already_reacted" || - record.data?.error === "already_reacted" - ) { - return true; - } - for (const value of [record.cause, record.original, record.originalError]) { - pending.push({ depth: current.depth + 1, value }); - } - } - return false; -} - -function providerEffectPayload( - payload: Record, -): ProviderEffectPayload | null { - if ( - payload.version !== 1 || - (payload.effect !== "thread_message" && - payload.effect !== "ephemeral_message" && - payload.effect !== "telegram_callback_notice") || - typeof payload.threadId !== "string" || - !payload.threadId || - typeof payload.text !== "string" || - !payload.text || - typeof payload.settleDelivery !== "boolean" || - typeof payload.runtimeGeneration !== "number" || - !Number.isSafeInteger(payload.runtimeGeneration) || - typeof payload.credentialFingerprint !== "string" - ) { - return null; - } - if ( - payload.effect === "ephemeral_message" && - (typeof payload.userId !== "string" || !payload.userId) - ) { - return null; - } - for (const key of [ - "authorizationMode", - "userId", - "fallbackText", - "completeConversationId", - "resourceId", - ] as const) { - const value = payload[key]; - if (value !== undefined && (typeof value !== "string" || !value)) { - return null; - } - } - if ( - payload.authorizationMode !== undefined && - payload.authorizationMode !== "principal" && - payload.authorizationMode !== "safe_notice" - ) { - return null; - } - if (payload.effect === "telegram_callback_notice") { - const receipt = parseTelegramCallbackReceipt(payload.telegramCallback); - if ( - !receipt || - payload.authorizationMode !== "safe_notice" || - payload.text !== TELEGRAM_PRIVATE_ACTION_UNAVAILABLE || - payload.threadId !== telegramCallbackThreadId(receipt) || - payload.userId !== receipt.receiverUserId || - payload.settleDelivery !== false || - payload.fallbackText !== undefined || - payload.completeConversationId !== undefined || - typeof payload.telegramAuthorizationSha256 !== "string" || - !/^[a-f0-9]{64}$/.test(payload.telegramAuthorizationSha256) - ) - return null; - } else if ( - payload.telegramCallback !== undefined || - payload.telegramAuthorizationSha256 !== undefined - ) - return null; - return payload as ProviderEffectPayload; -} export interface ChatChannelServiceOptions { /** @@ -1489,15 +1106,6 @@ function safeTitle(text: string, fallback: string): string { return (line || fallback).slice(0, 160); } -function slackThreadChannelId(threadId: string): string | null { - if (!threadId.startsWith("slack:")) return null; - const channelAndThread = threadId.slice("slack:".length); - const separator = channelAndThread.indexOf(":"); - const channelId = - separator === -1 ? channelAndThread : channelAndThread.slice(0, separator); - return channelId || null; -} - function actionThreadMatchesConversation( provider: ChatProvider, actionThreadId: string, @@ -1514,24 +1122,6 @@ function actionThreadMatchesConversation( ); } -function slackTaskStartActivityStatus(action: { - status: string; - updatedAt: Date; -}): string { - const age = Date.now() - action.updatedAt.getTime(); - if (action.status === "validating" && age >= SLACK_COMMAND_POST_STALE_MS) { - return "queued"; - } - if ( - (action.status === "received" && age >= SLACK_COMMAND_POST_STALE_MS) || - (action.status === "resolving" && - age >= SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS) - ) { - return "delivery_unknown"; - } - return action.status; -} - function sanitizeFilename(value: string | undefined): string | null { if (!value) return null; const leaf = value.replaceAll("\\", "/").split("/").pop()?.trim(); @@ -1821,163 +1411,6 @@ export function createChatSdkStatePersistence(db: Db): ChatSdkStatePersistence { }; } -const OUTBOUND_ATTACHMENT_STORAGE_TIMEOUT_MS = 10_000; - -class OutboundAttachmentHydrationError extends Error { - readonly code = "CHAT_ATTACHMENT_HYDRATION_FAILED"; - - constructor(message: string, cause?: unknown) { - super(message, cause === undefined ? undefined : { cause }); - this.name = "OutboundAttachmentHydrationError"; - } -} - -class OutboundAttachmentValidationError extends Error { - readonly code = "CHAT_ATTACHMENT_VALIDATION_FAILED"; - - constructor(message: string) { - super(message); - this.name = "OutboundAttachmentValidationError"; - } -} - -function isOutboundAttachmentHydrationError( - error: unknown, -): error is OutboundAttachmentHydrationError { - return error instanceof OutboundAttachmentHydrationError; -} - -function isOutboundAttachmentValidationError( - error: unknown, -): error is OutboundAttachmentValidationError { - return error instanceof OutboundAttachmentValidationError; -} - -/** - * Read one already-authorized attachment before any provider transport starts. - * The database digest, rather than mutable object-store metadata, is the - * integrity boundary. Both acquisition and streaming are bounded so a stuck - * storage backend cannot hold the publication lane indefinitely. - */ -export async function hydrateOutboundAttachment(input: { - storage: StorageService; - companyId: string; - objectKey: string; - byteSize: number; - sha256: string; - filename: string; - mimeType: string; - timeoutMs?: number; -}): Promise { - const timeoutMs = - typeof input.timeoutMs === "number" && - Number.isFinite(input.timeoutMs) && - input.timeoutMs > 0 - ? input.timeoutMs - : OUTBOUND_ATTACHMENT_STORAGE_TIMEOUT_MS; - if ( - !Number.isSafeInteger(input.byteSize) || - input.byteSize <= 0 || - input.byteSize > MAX_ATTACHMENT_BYTES || - !/^[0-9a-f]{64}$/iu.test(input.sha256) - ) { - throw new OutboundAttachmentHydrationError( - "Chat publication attachment metadata is invalid", - ); - } - const acquisitionTimeoutError = new OutboundAttachmentHydrationError( - "Chat publication attachment storage read timed out", - ); - let acquisitionTimedOut = false; - let rejectAcquisition!: (error: Error) => void; - const acquisitionTimeout = new Promise((_resolve, reject) => { - rejectAcquisition = reject; - }); - const acquisitionTimer = setTimeout(() => { - acquisitionTimedOut = true; - rejectAcquisition(acquisitionTimeoutError); - }, timeoutMs); - acquisitionTimer.unref?.(); - const objectPromise = Promise.resolve() - .then(() => input.storage.getObject(input.companyId, input.objectKey)) - .then((object) => { - if (acquisitionTimedOut) object.stream.destroy(); - return object; - }) - .catch((error) => { - throw isOutboundAttachmentHydrationError(error) - ? error - : new OutboundAttachmentHydrationError( - "Chat publication attachment storage read failed", - error, - ); - }); - let object: Awaited>; - try { - object = await Promise.race([objectPromise, acquisitionTimeout]); - } finally { - clearTimeout(acquisitionTimer); - } - - if ( - object.contentLength !== undefined && - object.contentLength !== input.byteSize - ) { - object.stream.destroy(); - throw new OutboundAttachmentHydrationError( - "Chat publication attachment size changed after registration", - ); - } - - const streamTimeoutError = new OutboundAttachmentHydrationError( - "Chat publication attachment storage read timed out", - ); - const streamTimer = setTimeout(() => { - object.stream.destroy(streamTimeoutError); - }, timeoutMs); - streamTimer.unref?.(); - const chunks: Buffer[] = []; - const digest = createHash("sha256"); - let bytes = 0; - try { - for await (const chunk of object.stream) { - const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - bytes += buffer.length; - if (bytes > input.byteSize || bytes > MAX_ATTACHMENT_BYTES) { - object.stream.destroy(); - throw new OutboundAttachmentHydrationError( - "Chat publication attachment exceeded its streaming byte limit", - ); - } - digest.update(buffer); - chunks.push(buffer); - } - } catch (error) { - throw isOutboundAttachmentHydrationError(error) - ? error - : new OutboundAttachmentHydrationError( - "Chat publication attachment storage read failed", - error, - ); - } finally { - clearTimeout(streamTimer); - } - - const actualDigest = digest.digest("hex"); - if (bytes !== input.byteSize || actualDigest !== input.sha256.toLowerCase()) { - if (!object.stream.destroyed) object.stream.destroy(); - throw new OutboundAttachmentHydrationError( - "Chat publication attachment integrity changed after registration", - ); - } - - return { - data: Buffer.concat(chunks, bytes), - filename: input.filename, - mimeType: input.mimeType, - }; -} - export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const runtime = options.runtime ?? createChatRuntime(); const githubManualMessages = new WeakMap(); @@ -2009,7 +1442,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { // An explicit webhook ingress remains separate from board/identity links. const getPublicBaseUrl = () => runtimeCanonicalOrigin() ?? configuredPublicBaseUrl; const { listPrincipals, createLinkIntent, previewIdentityLink, requestIdentityAccess, confirmIdentityLink, revokeLink } = createChatIdentityLinks(db, { - endpointRecord, getPublicBaseUrl, stageProviderEffect, scheduleProviderEffect, + endpointRecord, getPublicBaseUrl, stageProviderEffect: (...args) => stageProviderEffect(...args), scheduleProviderEffect, confirmationRuntimeFor: async (endpoint) => runtimeContexts.get((await runtimeFor(endpoint)) as object), }); // Task links must validate the original configured URL before normalization @@ -2459,67 +1892,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); } - async function addReceiptReaction(input: { - deliveryId: string; - endpoint: EndpointRow; - message: Message; - runtimeContext?: LifecycleRuntimeFence; - thread: Thread; - }): Promise { - // GitHub acknowledges through one editable response comment. Continue to - // drain legacy eyes removals, but never create new eyes for these requests. - if (input.endpoint.provider === "github") return; - const record = await endpointRecord(input.endpoint.id); - if (!record) return; - // A live SDK object remains bound to the credentials that authenticated - // its callback. Preserve that original fence rather than sampling the - // endpoint again after a rotation and accidentally blessing an obsolete - // adapter with the new generation. - const context = input.runtimeContext ?? runtimeContextForRecord(record); - const [inserted] = await db.transaction(async (tx) => { - await notifyChatActionWork(tx, "receipt_reaction"); - return tx - .insert(chatActions) - .values({ - companyId: input.endpoint.companyId, - endpointId: input.endpoint.id, - deliveryId: input.deliveryId, - kind: "receipt_reaction", - providerActionId: `receipt_reaction:${input.deliveryId}`, - payload: { - version: 1, - operation: "add", - threadId: input.thread.id, - messageId: input.message.id, - reaction: "eyes", - runtimeGeneration: context.generation, - credentialFingerprint: context.credentialFingerprint, - } satisfies ReceiptReactionPayload, - status: "received", - }) - .onConflictDoNothing() - .returning({ id: chatActions.id }); - }); - const actionId = - inserted?.id ?? - (await db - .select({ id: chatActions.id }) - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, input.endpoint.id), - eq( - chatActions.providerActionId, - `receipt_reaction:${input.deliveryId}`, - ), - ), - ) - .then((rows) => rows[0]?.id ?? null)); - if (!actionId) return; - await options.receiptReactionTransportBarrier?.(); - await processReceiptReaction(actionId); - } - function conversationDrainKey(endpointId: string, threadId: string) { return `${endpointId}:${createHash("sha256").update(durableExternalThreadIdentity(threadId)).digest("hex")}`; } @@ -2588,506 +1960,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .then((rows) => rows[0] ?? null); } - async function processReceiptReaction(actionId: string): Promise { - const initialAction = await db - .select({ endpointId: chatActions.endpointId }) - .from(chatActions) - .where( - and( - eq(chatActions.id, actionId), - eq(chatActions.kind, "receipt_reaction"), - ), - ) - .then((rows) => rows[0] ?? null); - if (!initialAction) return; - const initialEndpoint = await endpointRecord(initialAction.endpointId); - if (!initialEndpoint) return; - // Receipt reactions are GitHub repository mutations too. Serialize their - // final runtime-fence check and provider call with reconnect, secret - // rotation, pause, and removal so credentials cannot become obsolete - // between authorization and transport. - await withCredentialMutationLease(initialEndpoint.endpoint, (lease) => - processReceiptReactionWithCredentialLease(actionId, lease), - ); - } - - async function processReceiptReactionWithCredentialLease( - actionId: string, - credentialLease: CredentialMutationLeaseGuard, - ): Promise { - let action = await db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.id, actionId), - eq(chatActions.kind, "receipt_reaction"), - ), - ) - .then((rows) => rows[0] ?? null); - if (!action || ["processed", "cancelled"].includes(action.status)) return; - const payload = receiptReactionPayload(action.payload); - if (!payload) { - await db.transaction(async (tx) => { - await notifyChatActionWork(tx, "receipt_reaction"); - return tx - .update(chatActions) - .set({ - status: "failed", - result: { - code: "receipt_reaction_payload_invalid", - retryable: false, - }, - updatedAt: new Date(), - }) - .where(eq(chatActions.id, action.id)); - }); - return; - } - if (payload.operation === "add" && action.deliveryId) { - const terminalRemoval = await db - .select({ id: chatActions.id }) - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, action.endpointId), - eq( - chatActions.providerActionId, - `receipt_reaction_remove:${action.deliveryId}`, - ), - ), - ) - .then((rows) => rows[0] ?? null); - if (terminalRemoval) { - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - await tx - .update(chatActions) - .set({ - status: "cancelled", - result: { - ...(typeof action!.result?.attempts === "number" - ? { attempts: action!.result.attempts } - : {}), - code: "receipt_reaction_superseded_by_terminal_publication", - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.status, action!.status), - ), - ); - await credentialLease.assertOwned(tx); - }); - return; - } - } - if (action.status === "processing") { - if (action.updatedAt > new Date(Date.now() - PROVIDER_EFFECT_STALE_MS)) - return; - // Adding or removing the same reaction is idempotent. A worker - // interruption can be retried safely without an ambiguous - // duplicate-message boundary. - await db.transaction(async (tx) => { - await notifyChatActionWork(tx, "receipt_reaction"); - return tx - .update(chatActions) - .set({ - status: "failed", - result: { - ...(action.result ?? {}), - retryable: true, - retryAt: new Date().toISOString(), - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action.id), - eq(chatActions.status, "processing"), - eq(chatActions.updatedAt, action.updatedAt), - ), - ); - }); - action = await db - .select() - .from(chatActions) - .where(eq(chatActions.id, action.id)) - .then((rows) => rows[0] ?? null); - if (!action) return; - } - if (action.status === "failed") { - if (action.result?.retryable !== true) return; - const retryAt = - typeof action.result.retryAt === "string" - ? new Date(action.result.retryAt) - : null; - if (retryAt && retryAt > new Date()) return; - } else if (action.status !== "received") { - return; - } - const attempt = - (typeof action.result?.attempts === "number" ? action.result.attempts : 0) + - 1; - const retainedGitHubReceipt = parseGitHubReceiptIdentity( - action.result?.githubReceipt, - ); - const [claimed] = await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - const rows = await tx - .update(chatActions) - .set({ - status: "processing", - result: { - attempts: attempt, - ...(retainedGitHubReceipt - ? { githubReceipt: retainedGitHubReceipt } - : {}), - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.status, action!.status), - ), - ) - .returning(); - await credentialLease.assertOwned(tx); - return rows; - }); - if (!claimed) return; - - let reactionProvider: ChatProvider | null = null; - let githubReceipt: GitHubReceiptIdentity | null = retainedGitHubReceipt; - try { - const record = await endpointRecord(action.endpointId); - if ( - !record || - !["verifying", "active"].includes(record.endpoint.status) || - runtimeGeneration(record.endpoint.setup) !== payload.runtimeGeneration || - credentialFingerprint(record.credentialSecretRefs) !== - payload.credentialFingerprint - ) { - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - await tx - .update(chatActions) - .set({ - status: "cancelled", - result: { - attempts: attempt, - code: "receipt_reaction_runtime_superseded", - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ); - await credentialLease.assertOwned(tx); - }); - return; - } - reactionProvider = record.endpoint.provider; - // Resolve the adapter only after acquiring the credential lease and - // proving the action's durable generation/fingerprint is still current. - // Never reuse the live callback's adapter: it may hold an installation - // token minted by a superseded private key. - const endpointRuntime = await runtimeFor(record.endpoint); - const adapter = endpointRuntime.thread(payload.threadId).adapter; - await credentialLease.assertOwned(); - if (record.endpoint.provider === "slack") { - await endpointRuntime.applySlackReceiptReaction(payload, fetchImpl); - } else if (record.endpoint.provider === "github") { - const assertCurrent = async () => { - await credentialLease.assertOwned(); - const [current] = await db - .select({ endpoint: chatEndpoints, connection: toolConnections }) - .from(chatEndpoints) - .innerJoin( - toolConnections, - and( - eq(toolConnections.id, chatEndpoints.connectionId), - eq(toolConnections.companyId, chatEndpoints.companyId), - ), - ) - .where( - and( - eq(chatEndpoints.id, action!.endpointId), - eq(chatEndpoints.companyId, action!.companyId), - ), - ) - .limit(1); - if ( - !current || - !["active", "verifying"].includes(current.endpoint.status) || - !current.connection.enabled || - current.connection.status !== "active" || - runtimeGeneration(current.endpoint.setup) !== - payload.runtimeGeneration || - credentialFingerprint(current.connection.credentialSecretRefs) !== - payload.credentialFingerprint || - runtime.get(action!.endpointId) !== endpointRuntime - ) - throw new Error("GitHub receipt authority is no longer current"); - }; - const applied = await endpointRuntime.applyGitHubReceiptReaction( - { ...payload, ...(githubReceipt ? { githubReceipt } : {}) }, - assertCurrent, - fetchImpl, - ); - githubReceipt = parseGitHubReceiptIdentity(applied); - if ( - !githubReceipt || - (payload.operation === "add" && !githubReceipt.reactionId) - ) - throw new Error("GitHub receipt identity was not confirmed"); - } else if (payload.operation === "remove") { - await adapter.removeReaction( - payload.threadId, - payload.messageId, - payload.reaction, - ); - } else { - await adapter.addReaction( - payload.threadId, - payload.messageId, - payload.reaction, - ); - } - // The provider may already have accepted the idempotent reaction. Fence - // the local success receipt so a reclaimed lease cannot report success - // for an obsolete runtime. - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - const processed = await tx - .update(chatActions) - .set({ - status: "processed", - result: { - attempts: attempt, - ...(githubReceipt ? { githubReceipt } : {}), - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (!processed.length) { - throw new Error( - "Receipt-reaction ownership changed before success settlement", - ); - } - if (action.deliveryId) { - await tx - .update(chatDeliveries) - .set({ redactedError: null, updatedAt: new Date() }) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.state, "processed"), - like(chatDeliveries.redactedError, "Receipt reaction %"), - ), - ); - } - await credentialLease.assertOwned(tx); - }); - } catch (error) { - if ( - payload.operation === "add" && - reactionProvider === "slack" && - isSlackReceiptReactionAlreadyApplied(error) - ) { - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - const processed = await tx - .update(chatActions) - .set({ - status: "processed", - result: { - attempts: attempt, - code: "receipt_reaction_already_applied", - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (!processed.length) { - throw new Error( - "Receipt-reaction ownership changed before idempotent settlement", - ); - } - if (action.deliveryId) { - await tx - .update(chatDeliveries) - .set({ redactedError: null, updatedAt: new Date() }) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.state, "processed"), - like(chatDeliveries.redactedError, "Receipt reaction %"), - ), - ); - } - await credentialLease.assertOwned(tx); - }); - return; - } - const disposition = classifyChatPublicationError(error, attempt); - const retryable = - disposition.kind === "retry" || - (disposition.kind === "delivery_unknown" && attempt < 3); - const retryAfterMs = - disposition.kind === "retry" - ? disposition.retryAfterMs - : 100 * 2 ** Math.max(0, attempt - 1); - const operationLabel = - payload.operation === "remove" ? " reaction removal" : " reaction"; - const diagnostic = - `Receipt${operationLabel} ${retryable ? "deferred" : "failed"} after ${attempt} attempt${attempt === 1 ? "" : "s"} (${disposition.kind}): ${redactError(error)}`.slice( - 0, - MAX_ERROR_TEXT, - ); - const retryAt = retryable - ? new Date(Date.now() + retryAfterMs).toISOString() - : null; - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - await notifyChatActionWork(tx, "receipt_reaction"); - const [failed] = await tx - .update(chatActions) - .set({ - status: "failed", - result: { - attempts: attempt, - ...(githubReceipt ? { githubReceipt } : {}), - code: - payload.operation === "remove" - ? `receipt_reaction_removal_${disposition.kind}` - : `receipt_reaction_${disposition.kind}`, - redactedError: diagnostic, - retryable, - retryAt, - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (failed && action.deliveryId) { - await tx - .update(chatDeliveries) - .set({ redactedError: diagnostic, updatedAt: new Date() }) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.state, "processed"), - ), - ); - } - await credentialLease.assertOwned(tx); - }); - logger.warn( - { - endpointId: action.endpointId, - deliveryId: action.deliveryId, - attempts: attempt, - disposition: disposition.kind, - operation: payload.operation, - retryAt, - }, - "external chat receipt reaction deferred", - ); - } - } - - async function processPendingReceiptReactions( - limit = 25, - onlyActionId?: string, - ) { - const actions = await db.select({ id: chatActions.id }).from(chatActions) - .where(and(dueChatAction("receipt_reaction"), onlyActionId ? eq(chatActions.id, onlyActionId) : undefined)) - .orderBy(asc(chatActions.createdAt)).limit(limit); - for (const action of actions) await processReceiptReaction(action.id); - return actions.length; - } - - async function stageProviderEffect( - database: DbOrTransaction, - input: { - endpoint: EndpointRow; - deliveryId?: string | null; - conversationId?: string | null; - principalId?: string | null; - providerActionId: string; - payload: Omit< - ProviderEffectPayload, - "runtimeGeneration" | "credentialFingerprint" - >; - runtimeContext: LifecycleRuntimeFence; - }, - ) { - await notifyChatActionWork(database, "provider_effect"); - const [inserted] = await database - .insert(chatActions) - .values({ - companyId: input.endpoint.companyId, - endpointId: input.endpoint.id, - deliveryId: input.deliveryId ?? null, - conversationId: input.conversationId ?? null, - principalId: input.principalId ?? null, - kind: "provider_effect", - providerActionId: input.providerActionId, - payload: { - ...input.payload, - runtimeGeneration: input.runtimeContext.generation, - credentialFingerprint: input.runtimeContext.credentialFingerprint, - }, - status: "received", - }) - .onConflictDoNothing() - .returning(); - if (inserted) return inserted; - return database - .select() - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, input.endpoint.id), - eq(chatActions.providerActionId, input.providerActionId), - eq(chatActions.kind, "provider_effect"), - ), - ) - .then((rows) => rows[0] ?? null); - } - async function stageAuthorizedTaskControlPublication( tx: DbOrTransaction, input: { @@ -3145,809 +2017,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return publication; } - function providerEffectDisposition( - error: unknown, - attempt: number, - providerEffectCompleted: boolean, - ) { - if (!providerEffectCompleted) - return classifyChatPublicationError(error, attempt); - return { - kind: "delivery_unknown" as const, - reason: - "Provider effect completed, but Paperclip could not confirm its durable result", - }; - } - - async function finalizeProviderEffectFailure(input: { - action: typeof chatActions.$inferSelect; - endpoint: EndpointRow; - payload: ProviderEffectPayload; - disposition: ReturnType; - attempt: number; - }): Promise { - const failure = redactSensitiveText(input.disposition.reason).slice( - 0, - MAX_ERROR_TEXT, - ); - const retryable = input.disposition.kind === "retry"; - const retryAt = - input.disposition.kind === "retry" - ? new Date(Date.now() + input.disposition.retryAfterMs) - : null; - let invalidateCurrentRuntime = false; - const finalized = await db.transaction(async (tx) => { - // Admission locks the endpoint before its delivery. Keep that order - // when failure settlement also needs to change endpoint/resource health. - const currentEndpoint = - input.disposition.kind === "endpoint_attention" || - input.disposition.kind === "resource_unavailable" - ? await runtimeCallbackEndpoint( - tx, - input.endpoint.id, - { - generation: input.payload.runtimeGeneration, - credentialFingerprint: input.payload.credentialFingerprint, - }, - ["verifying", "active"], - ) - : null; - await notifyChatActionWork(tx, "provider_effect"); - const [ownedAction] = await tx - .update(chatActions) - .set({ - status: - input.disposition.kind === "delivery_unknown" - ? "delivery_unknown" - : "failed", - result: { - code: `provider_effect_${input.disposition.kind}`, - attempts: input.attempt, - redactedError: failure, - retryable, - retryAt: retryAt?.toISOString() ?? null, - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, input.action.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${input.attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (!ownedAction) return false; - if (input.payload.settleDelivery && input.action.deliveryId) { - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set({ - state: retryable ? "retry" : "failed", - nextAttemptAt: retryAt, - redactedError: - input.disposition.kind === "delivery_unknown" - ? `Provider response is unconfirmed: ${failure}`.slice( - 0, - MAX_ERROR_TEXT, - ) - : failure, - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.id, input.action.deliveryId), - eq(chatDeliveries.state, "processing"), - ), - ); - } - if (input.disposition.kind === "endpoint_attention" && currentEndpoint) { - await tx - .update(chatEndpoints) - .set({ - status: "attention", - healthMessage: "Provider credentials or permissions need attention", - lastError: failure, - updatedAt: new Date(), - }) - .where(eq(chatEndpoints.id, currentEndpoint.id)); - await tx - .update(toolConnections) - .set({ - status: "disabled", - enabled: false, - healthStatus: "degraded", - healthMessage: "Provider credentials or permissions need attention", - lastError: failure, - healthCheckedAt: new Date(), - updatedAt: new Date(), - }) - .where(eq(toolConnections.id, currentEndpoint.connectionId)); - invalidateCurrentRuntime = true; - } else if ( - input.disposition.kind === "resource_unavailable" && - input.payload.resourceId && - currentEndpoint - ) { - await tx - .update(chatEndpointResources) - .set({ availability: "unavailable", updatedAt: new Date() }) - .where( - and( - eq(chatEndpointResources.id, input.payload.resourceId), - eq(chatEndpointResources.endpointId, currentEndpoint.id), - ), - ); - } - return true; - }); - if (finalized && invalidateCurrentRuntime) { - await invalidateRuntime(input.endpoint.id).catch(() => undefined); - } - return finalized; - } - - async function quarantineStaleProviderEffect( - action: typeof chatActions.$inferSelect, - ) { - const payload = providerEffectPayload(action.payload); - const failure = - "Provider delivery could not be confirmed after the worker stopped. The effect will not be replayed automatically."; - await db.transaction(async (tx) => { - await notifyChatActionWork(tx, "provider_effect"); - const [quarantined] = await tx - .update(chatActions) - .set({ - status: "delivery_unknown", - result: { - code: "provider_effect_delivery_unknown", - redactedError: failure, - retryable: false, - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action.id), - eq(chatActions.status, "processing"), - eq(chatActions.updatedAt, action.updatedAt), - sql`(${chatActions.result}->>'attempts')::int = ${ - typeof action.result?.attempts === "number" - ? action.result.attempts - : -1 - }`, - lte( - chatActions.updatedAt, - new Date(Date.now() - PROVIDER_EFFECT_STALE_MS), - ), - ), - ) - .returning({ id: chatActions.id }); - if (quarantined && payload?.settleDelivery && action.deliveryId) { - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set({ - state: "failed", - nextAttemptAt: null, - redactedError: failure, - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.state, "processing"), - ), - ); - } - }); - } - - function providerEffectThreadResourceId( - provider: ChatProvider, - threadId: string, - ): string | null { - if (provider === "slack") { - return ( - slackThreadChannelId(threadId) ?? - (!threadId.includes(":") ? threadId : null) - ); - } - if (provider === "telegram") { - return /^telegram:([^:]+)/.exec(threadId)?.[1] ?? null; - } - if (provider === "github") { - return /^github:([^:]+)/.exec(threadId)?.[1]?.toLowerCase() ?? null; - } - if (provider === "discord") { - return /^discord:[^:]+:([^:]+)/.exec(threadId)?.[1] ?? null; - } - const conversationId = teamsConversationId(threadId); - return conversationId ? baseTeamsConversationId(conversationId) : null; - } - - // This is permission to return one fixed, neutral callback denial, not - // permission to execute the denied action or publish to the conversation. - // Retain the initial identity-policy snapshot so queued notices cannot cross - // a subsequent actor/link/reach change. No provider I/O occurs under locks. - async function lockTelegramCallbackNoticeAuthority( - tx: DbTransaction, - endpoint: EndpointRow, - receipt: TelegramCallbackReceipt, - principalId: string | null, - ): Promise { - if ( - endpoint.provider !== "telegram" || - endpoint.status !== "active" || - endpoint.companyId !== receipt.companyId || - endpoint.id !== receipt.endpointId || - endpoint.botExternalId !== receipt.botUserId || - !endpoint.providerAccountId || - !principalId || - Date.now() < receipt.receivedAtMs || - Date.now() >= receipt.deadlineAtMs - ) - return null; - const resource = - receipt.chatType === "private" - ? null - : await tx - .select() - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.companyId, endpoint.companyId), - eq(chatEndpointResources.endpointId, endpoint.id), - eq(chatEndpointResources.providerResourceId, receipt.chatId), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - if ( - receipt.chatType === "private" - ? !endpoint.allowDirectMessages - : !resource || !nonDirectDestinationAllowed(endpoint, resource) - ) - return null; - const principal = await tx - .select() - .from(chatExternalPrincipals) - .where( - and( - eq(chatExternalPrincipals.companyId, endpoint.companyId), - eq(chatExternalPrincipals.provider, endpoint.provider), - eq( - chatExternalPrincipals.providerAccountId, - endpoint.providerAccountId, - ), - eq(chatExternalPrincipals.id, principalId), - eq(chatExternalPrincipals.externalId, receipt.receiverUserId), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - if (!principal || principal.kind !== "user" || principal.isBot) return null; - const authorization = await lockCurrentPrincipalAuthorization( - tx, - endpoint, - principal.id, - ); - const link = await tx - .select({ - status: chatIdentityLinks.status, - userId: chatIdentityLinks.paperclipUserId, - }) - .from(chatIdentityLinks) - .where( - and( - eq(chatIdentityLinks.companyId, endpoint.companyId), - eq(chatIdentityLinks.endpointId, endpoint.id), - eq(chatIdentityLinks.principalId, principal.id), - ), - ) - .then((rows) => rows[0] ?? null); - return createHash("sha256") - .update( - JSON.stringify([ - endpoint.companyId, - endpoint.id, - receipt.botUserId, - resource?.id ?? null, - principal.id, - principal.externalId, - link, - authorization, - endpoint.allowUnlinkedPeople, - endpoint.sponsorUserId, - ]), - ) - .digest("hex"); - } - - async function lockCurrentProviderEffectAuthorization( - tx: DbTransaction, - action: typeof chatActions.$inferSelect, - endpoint: EndpointRow, - payload: ProviderEffectPayload, - ): Promise { - if (payload.effect === "telegram_callback_notice") { - const receipt = parseTelegramCallbackReceipt(payload.telegramCallback); - if (!receipt || !action.deliveryId) return false; - const delivery = await tx - .select() - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.companyId, action.companyId), - eq(chatDeliveries.endpointId, action.endpointId), - eq(chatDeliveries.state, "filtered"), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - const retained = - delivery && - parseTelegramCallbackReceipt(delivery.normalizedEvent.telegramCallback); - if ( - !delivery || - delivery.principalId !== action.principalId || - retained?.sourceSha256 !== receipt.sourceSha256 || - delivery.normalizedEvent.telegramAuthorizationSha256 !== - payload.telegramAuthorizationSha256 - ) - return false; - return ( - (await lockTelegramCallbackNoticeAuthority( - tx, - endpoint, - receipt, - action.principalId, - )) === payload.telegramAuthorizationSha256 - ); - } - const conversation = action.conversationId - ? await tx - .select() - .from(chatConversations) - .where( - and( - eq(chatConversations.companyId, endpoint.companyId), - eq(chatConversations.endpointId, endpoint.id), - eq(chatConversations.id, action.conversationId), - inArray(chatConversations.state, ["active", "waiting"]), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null) - : null; - if (action.conversationId && !conversation) return false; - const inferredResourceId = providerEffectThreadResourceId( - endpoint.provider, - payload.threadId, - ); - const resource = await tx - .select() - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.companyId, endpoint.companyId), - eq(chatEndpointResources.endpointId, endpoint.id), - payload.resourceId || conversation?.resourceId - ? eq( - chatEndpointResources.id, - payload.resourceId ?? conversation!.resourceId!, - ) - : inferredResourceId - ? eq(chatEndpointResources.providerResourceId, inferredResourceId) - : sql`false`, - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - const slackChannelId = - endpoint.provider === "slack" - ? slackThreadChannelId(payload.threadId) - : null; - const isDirectMessage = - conversation?.isDirectMessage === true || - resource?.type === "direct_message" || - Boolean(slackChannelId && /^D[A-Z0-9]+$/i.test(slackChannelId)); - const destinationAllowed = isDirectMessage - ? endpoint.allowDirectMessages - : nonDirectDestinationAllowed(endpoint, resource); - if (payload.authorizationMode === "safe_notice") return true; - if (!destinationAllowed) return false; - if (!action.principalId) return false; - return ( - await lockCurrentPrincipalAuthorization(tx, endpoint, action.principalId) - ).allowed; - } - - async function cancelUnauthorizedProviderEffect( - tx: DbTransaction, - action: typeof chatActions.$inferSelect, - attempt: number, - ): Promise { - await notifyChatActionWork(tx, "provider_effect"); - const [cancelled] = await tx - .update(chatActions) - .set({ - status: "cancelled", - result: { - attempts: attempt, - code: "provider_effect_no_longer_authorized", - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (!cancelled) return false; - if (action.deliveryId) { - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set({ - state: "filtered", - processedAt: new Date(), - nextAttemptAt: null, - redactedError: - "Provider reply was suppressed because current chat authorization changed", - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.id, action.deliveryId), - eq(chatDeliveries.state, "processing"), - ), - ); - } - return true; - } - - async function processProviderEffect( - actionId: string, - liveTarget?: ProviderEffectTarget, - ): Promise<"processed" | "pending" | "failed" | "delivery_unknown"> { - const initial = await db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.id, actionId), - eq(chatActions.kind, "provider_effect"), - ), - ) - .then((rows) => rows[0] ?? null); - if (!initial) return "failed"; - const initialEndpoint = await endpointRecord(initial.endpointId); - if (!initialEndpoint) return "failed"; - - const processClaimedEffect = async () => { - let action = await db - .select() - .from(chatActions) - .where(eq(chatActions.id, actionId)) - .then((rows) => rows[0] ?? null); - if (!action) return "failed"; - const payload = providerEffectPayload(action.payload); - if (!payload) { - await db.transaction(async (tx) => { - await notifyChatActionWork(tx, "provider_effect"); - return tx - .update(chatActions) - .set({ - status: "failed", - result: { code: "provider_effect_payload_invalid" }, - updatedAt: new Date(), - }) - .where(eq(chatActions.id, action.id)); - }); - return "failed"; - } - if (action.status === "processed") return "processed"; - if (action.status === "delivery_unknown") return "delivery_unknown"; - if (action.status === "processing") { - if (action.updatedAt > new Date(Date.now() - PROVIDER_EFFECT_STALE_MS)) - return "pending"; - await quarantineStaleProviderEffect(action); - return "delivery_unknown"; - } - if (action.status === "failed") { - if (action.result?.retryable !== true) return "failed"; - const retryAt = - typeof action.result.retryAt === "string" - ? new Date(action.result.retryAt) - : null; - if (retryAt && retryAt > new Date()) return "pending"; - } else if (action.status !== "received") { - return "failed"; - } - - const attempt = - (typeof action.result?.attempts === "number" - ? action.result.attempts - : 0) + 1; - const record = await endpointRecord(action.endpointId); - let providerEffectCompleted = false; - try { - if (!record) { - throw Object.assign( - new Error("Chat endpoint is unavailable for this provider effect"), - { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, - ); - } - return await withCredentialMutationLease( - record.endpoint, - async (credentialLease) => { - const claim = await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - const currentAction = await tx - .select() - .from(chatActions) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.kind, "provider_effect"), - eq(chatActions.status, action!.status), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - if (!currentAction) return null; - const currentEndpoint = await runtimeCallbackEndpoint( - tx, - currentAction.endpointId, - { - generation: payload.runtimeGeneration, - credentialFingerprint: payload.credentialFingerprint, - }, - ["verifying", "active"], - ); - const authorized = currentEndpoint - ? await lockCurrentProviderEffectAuthorization( - tx, - currentAction, - currentEndpoint, - payload, - ) - : false; - await notifyChatActionWork(tx, "provider_effect"); - const [transportClaim] = await tx - .update(chatActions) - .set({ - status: "processing", - result: { attempts: attempt }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, currentAction.id), - eq(chatActions.status, currentAction.status), - ), - ) - .returning(); - if (!transportClaim) return null; - if (!currentEndpoint || !authorized) { - const cancelled = await cancelUnauthorizedProviderEffect( - tx, - transportClaim, - attempt, - ); - if (!cancelled) { - throw new Error( - "Provider effect ownership changed before authorization cancellation", - ); - } - return { outcome: "cancelled" as const }; - } - await credentialLease.assertOwned(tx); - return { - action: transportClaim, - endpoint: currentEndpoint, - outcome: "claimed" as const, - }; - }); - if (!claim) return "pending"; - if (claim.outcome === "cancelled") return "failed"; - action = claim.action; - - // `processing` is the durable, single-owner transport claim and - // authorization linearization point. Its transaction has committed - // before provider I/O begins. The renewable credential lease keeps - // runtime identity stable without holding database row locks. - let sent: { id: string; threadId: string } | null = null; - if (payload.effect === "telegram_callback_notice") { - sent = await ( - await runtimeFor(claim.endpoint) - ).sendTelegramCallbackNotice( - payload.telegramCallback!, - payload.text, - ); - } else { - const target = - liveTarget ?? - (await runtimeFor(claim.endpoint)).thread(payload.threadId); - if (payload.effect === "ephemeral_message") { - if (!target.postEphemeral) { - if (!payload.fallbackText) { - throw Object.assign( - new Error("Provider does not support ephemeral messages"), - { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, - ); - } - sent = await target.post(payload.fallbackText); - } else { - try { - sent = await target.postEphemeral( - payload.userId!, - payload.text, - { fallbackToDM: false }, - ); - } catch (error) { - if ( - !payload.fallbackText || - classifyChatPublicationError(error, attempt).kind !== - "failed" - ) { - throw error; - } - } - if (!sent && payload.fallbackText) { - sent = await target.post(payload.fallbackText); - } - } - } else if (claim.endpoint.provider === "imessage-photon") { - const adapter = (await runtimeFor(claim.endpoint)).getProviderAdapter(); - if (!(adapter instanceof PhotonChatAdapter)) throw new Error("Photon runtime unavailable"); - const receipt = await adapter.publish(payload.threadId, `effect:${action.id}`, projectSafeChatPublicationText(payload.text), { - assertCurrent: async () => { await credentialLease.assertOwned(); }, - }); - sent = { id: receipt.id, threadId: payload.threadId }; - } else { - sent = await target.post(payload.text); - } - } - if (!sent) { - throw Object.assign( - new Error("Provider did not accept the message"), - { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, - ); - } - providerEffectCompleted = true; - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - // A duplicate webhook holds the endpoint while waiting for the - // delivery's unique-index entry. Lock this endpoint first too, - // before settling the action/conversation/delivery, so neither - // transaction can wait on a row already held by the other. - const currentEndpoint = await runtimeCallbackEndpoint( - tx, - claim.endpoint.id, - { - generation: payload.runtimeGeneration, - credentialFingerprint: payload.credentialFingerprint, - }, - ["verifying", "active"], - ); - await notifyChatActionWork(tx, "provider_effect"); - const [completed] = await tx - .update(chatActions) - .set({ - status: "processed", - result: { - attempts: attempt, - providerMessageId: sent.id, - threadId: sent.threadId, - ...(payload.effect === "telegram_callback_notice" - ? { - code: - payload.telegramCallback?.chatType === "private" - ? "telegram_private_api_accepted" - : "telegram_ephemeral_api_accepted", - } - : {}), - }, - updatedAt: new Date(), - }) - .where( - and( - eq(chatActions.id, action!.id), - eq(chatActions.status, "processing"), - sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, - ), - ) - .returning({ id: chatActions.id }); - if (!completed) { - throw new Error( - "Provider effect ownership changed before commit", - ); - } - if (payload.completeConversationId) { - await tx - .update(chatConversations) - .set({ state: "completed", updatedAt: new Date() }) - .where( - and( - eq(chatConversations.id, payload.completeConversationId), - eq(chatConversations.endpointId, claim.endpoint.id), - inArray(chatConversations.state, ["active", "waiting"]), - ), - ); - } - if (payload.settleDelivery && action!.deliveryId) { - await notifyChatDeliveryWork(tx); - const [settled] = await tx - .update(chatDeliveries) - .set({ - state: "processed", - processedAt: new Date(), - nextAttemptAt: null, - redactedError: null, - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.id, action!.deliveryId), - eq(chatDeliveries.state, "processing"), - ), - ) - .returning({ id: chatDeliveries.id }); - if (!settled) { - throw new Error( - "Provider effect delivery ownership changed before commit", - ); - } - } - if (currentEndpoint) { - await tx - .update(chatEndpoints) - .set({ lastEventAt: new Date(), updatedAt: new Date() }) - .where(eq(chatEndpoints.id, currentEndpoint.id)); - } - await credentialLease.assertOwned(tx); - }); - return "processed"; - }, - ); - } catch (error) { - const disposition = providerEffectDisposition( - error, - attempt, - providerEffectCompleted, - ); - try { - await finalizeProviderEffectFailure({ - action, - endpoint: record?.endpoint ?? initialEndpoint.endpoint, - payload, - disposition, - attempt, - }); - } catch (finalizationError) { - if (disposition.kind === "delivery_unknown") { - throw Object.assign( - new Error( - "Provider effect is ambiguous and its durable quarantine failed", - { cause: finalizationError }, - ), - { code: "CHAT_PROVIDER_EFFECT_AMBIGUOUS" }, - ); - } - throw finalizationError; - } - throw error; - } - }; - return processClaimedEffect(); - } - function scheduleProviderEffect( actionId: string, liveTarget?: ProviderEffectTarget, @@ -3957,36 +2026,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }); } - async function processPendingProviderEffects(limit = 25) { - const now = new Date(); - const staleBefore = new Date(now.getTime() - PROVIDER_EFFECT_STALE_MS); - const actions = await db - .select() - .from(chatActions) - .where(dueChatAction("provider_effect", now.getTime())) - .orderBy(asc(chatActions.createdAt)) - .limit(limit); - for (const action of actions) { - if (action.status === "processing" && action.updatedAt <= staleBefore) { - await quarantineStaleProviderEffect(action); - continue; - } - try { - await processProviderEffect(action.id); - } catch (error) { - logger.warn( - { - endpointId: action.endpointId, - actionId: action.id, - error: redactError(error), - }, - "chat provider effect reconciliation failed", - ); - } - } - return actions.length; - } - async function runtimeCallbackEndpoint( tx: DbTransaction, endpointId: string, @@ -4102,212 +2141,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); } - function discordGatewayFailureDetail( - event: DiscordGatewayCallbackEvent["event"], - ): string { - if (event.type === "guild_removed") { - return "Discord bot was removed from the configured server"; - } - if (event.type === "guild_unavailable") { - return "Discord server is temporarily unavailable; reconnecting"; - } - if (event.type === "disconnected") { - return `Discord Gateway disconnected${event.code === undefined ? "" : ` (code ${event.code})`}; reconnecting`; - } - if (event.type === "failure") { - const code = event.error.code ?? event.error.status; - return `Discord Gateway failed${code === undefined ? "" : ` (${String(code).slice(0, 64)})`}; ${event.fatal ? "credentials or privileged intents need attention" : "reconnecting"}`; - } - return "Discord Gateway connection needs attention"; - } - - function handleDiscordGatewayEvent( - callback: DiscordGatewayCallbackEvent, - context: RuntimeContext, - ): Promise { - const pending = (context.discordGatewayEventQueue ?? Promise.resolve()) - .catch(() => undefined) - .then(() => processDiscordGatewayEvent(callback, context)); - context.discordGatewayEventQueue = pending; - return pending; - } - - async function processDiscordGatewayEvent( - callback: DiscordGatewayCallbackEvent, - context: RuntimeContext, - ): Promise { - if ( - !(await ensureDiscordGatewayRuntimeIsCurrent( - callback.endpointId, - context, - )) - ) - return; - if (callback.sequence <= (context.discordGatewaySequence ?? 0)) return; - context.discordGatewaySequence = callback.sequence; - await options.discordGatewayEventBarrier?.(callback); - const record = await runtimeCallbackRecord(callback.endpointId, context, [ - "verifying", - "active", - "attention", - ]); - if (!record || record.endpoint.provider !== "discord") return; - const event = callback.event; - if (event.type === "channel_removed") { - await applyProviderLifecycleEffect( - record.endpoint, - { - kind: "resource", - provider: "discord", - providerEventId: `gateway:channel_removed:${event.channelId}`, - providerResourceId: event.channelId, - resourceType: "channel", - label: event.label?.trim() || event.channelId, - availability: "removed", - metadata: { source: "discord_gateway" }, - }, - context, - ); - return; - } - - await db.transaction(async (tx) => { - const current = await runtimeCallbackEndpoint( - tx, - callback.endpointId, - context, - ["verifying", "active", "attention"], - ); - if ( - !current || - current.provider !== "discord" || - !discordGatewayRuntimeIsCurrent(callback.endpointId, context) - ) { - return; - } - const now = new Date(); - if (event.type === "connecting") { - await tx - .update(chatEndpoints) - .set({ - healthMessage: "Discord Gateway connecting", - lastError: null, - lastEventAt: now, - updatedAt: now, - }) - .where(eq(chatEndpoints.id, current.id)); - await tx - .update(toolConnections) - .set({ - healthStatus: "unchecked", - healthMessage: "Discord Gateway connecting", - lastError: null, - healthCheckedAt: now, - updatedAt: now, - }) - .where(eq(toolConnections.id, current.connectionId)); - return; - } - if (event.type === "ready" || event.type === "guild_available") { - const gatewayOwnedAttention = - current.status === "attention" && - current.healthMessage?.startsWith("Discord ") === true; - if (current.status === "attention" && !gatewayOwnedAttention) return; - const status = - current.setup.step === "complete" ? "active" : "verifying"; - if (status !== current.status) await notifyChatEndpointWork(tx); - await tx - .update(chatEndpoints) - .set({ - status, - healthMessage: - status === "active" - ? "Connected" - : "Waiting for a test conversation", - lastError: null, - lastEventAt: now, - updatedAt: now, - }) - .where(eq(chatEndpoints.id, current.id)); - await tx - .update(toolConnections) - .set({ - status: "active", - enabled: true, - healthStatus: "healthy", - healthMessage: "Connected", - lastError: null, - healthCheckedAt: now, - updatedAt: now, - }) - .where(eq(toolConnections.id, current.connectionId)); - return; - } - - const detail = discordGatewayFailureDetail(event); - const transientGuildOutage = event.type === "guild_unavailable"; - const terminal = - event.type === "guild_removed" || - ((event.type === "failure" || event.type === "disconnected") && - event.fatal); - await tx - .update(chatEndpoints) - .set({ - status: transientGuildOutage - ? current.status - : event.type === "guild_removed" - ? "revoked" - : "attention", - healthMessage: detail, - lastError: detail, - lastEventAt: now, - ...(terminal - ? { - setup: { - ...current.setup, - runtimeGeneration: runtimeGeneration(current.setup) + 1, - } as InternalSetupState, - } - : {}), - updatedAt: now, - }) - .where(eq(chatEndpoints.id, current.id)); - await tx - .update(toolConnections) - .set({ - status: terminal ? "disabled" : "active", - enabled: !terminal, - healthStatus: event.type === "guild_removed" ? "failed" : "degraded", - healthMessage: detail, - lastError: detail, - healthCheckedAt: now, - updatedAt: now, - }) - .where(eq(toolConnections.id, current.connectionId)); - if (event.type === "guild_removed") { - await tx - .update(chatEndpointResources) - .set({ availability: "unavailable", updatedAt: now }) - .where( - and( - eq(chatEndpointResources.companyId, current.companyId), - eq(chatEndpointResources.endpointId, current.id), - ), - ); - await tx - .update(chatConversations) - .set({ state: "unavailable", updatedAt: now }) - .where( - and( - eq(chatConversations.companyId, current.companyId), - eq(chatConversations.endpointId, current.id), - inArray(chatConversations.state, ["active", "waiting"]), - ), - ); - } - }); - } - function serializeEndpoint( row: Awaited>, ): ChatEndpoint { @@ -5270,6 +3103,16 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } const withCredentialMutationLease = chatCredentialMutationLease(db, options); + const { setupTestStatus, test } = createSetupProbes(db, { endpointRecord, withCredentialMutationLease, fetchImpl, findAuthorizedIdentityLink, runtimeGeneration, lockCurrentPrincipalAuthorization, get, setupTestActivationBarrier: () => options.setupTestActivationBarrier?.() ?? Promise.resolve() }); + const { listConversations, listActivity, listActivityPage } = createChatActivity(db, { endpointRecord, teamsTransferProtocol }); + const { lockTelegramCallbackNoticeAuthority } = createTelegramCallbackNoticeAuthority({ nonDirectDestinationAllowed, lockCurrentPrincipalAuthorization }); + const { stageProviderEffect, processProviderEffect, processPendingProviderEffects } = createProviderEffects(db, { endpointRecord, runtimeCallbackEndpoint, withCredentialMutationLease, runtimeFor, invalidateRuntime, lockCurrentPrincipalAuthorization, redactError, MAX_ERROR_TEXT, nonDirectDestinationAllowed, lockTelegramCallbackNoticeAuthority }); + const { publicationFiles } = createOutboundAttachments(db, { get storage() { return options.storage; } }); + const { ingestAttachments, attachmentOmissionDetail } = createInboundAttachments(db, { authorizeInboundWakeup, lifecycleRuntimeFence, sanitizeFilename, issuesSvc, isExternalActionAuthorizationChange, redactError, get storage() { return options.storage; } }); + const { handleReaction, processPendingReactionDeliveries } = createInboundReactions(db, { lockCurrentPrincipalAuthorization, runtimeCallbackRecord, runtimeCallbackEndpoint, ensurePrincipal, durableExternalThreadIdentity, recordCurrentMicrosoftTeamsRoute, lifecycleRuntimeFence, deliveryReady, redactError, nonDirectDestinationAllowed, reactionLinkPreflightBarrier: () => options.reactionLinkPreflightBarrier?.() ?? Promise.resolve(), reactionReplayEndpointLockBarrier: () => options.reactionReplayEndpointLockBarrier?.() ?? Promise.resolve(), reactionReplayConversationLockBarrier: () => options.reactionReplayConversationLockBarrier?.() ?? Promise.resolve() }); + const { addReceiptReaction, processReceiptReaction, processPendingReceiptReactions } = createReceiptReactions(db, { endpointRecord, runtimeContextForRecord, withCredentialMutationLease, runtimeFor, fetchImpl, runtime, runtimeGeneration, credentialFingerprint, redactError, MAX_ERROR_TEXT, receiptReactionTransportBarrier: () => options.receiptReactionTransportBarrier?.() ?? Promise.resolve() }); + const { admitDiscordRootMention, discordRootThreadPending, reconcileDiscordRootThread } = createDiscordRootThreads(db, { ensureDiscordGatewayRuntimeIsCurrent, withCredentialMutationLease, runtimeCallbackEndpoint, lockCurrentPrincipalAuthorization, processMessage, redactError, scheduleConversationDrain, lifecycleRuntimeFence, runtimeFor, DISCORD_ROOT_THREAD_CONFIRMATION_DELAY_MS, discordRootThreadTransportBarrier: () => options.discordRootThreadTransportBarrier?.() ?? Promise.resolve() }); + const { handleDiscordGatewayEvent } = createDiscordGatewayEvents(db, { ensureDiscordGatewayRuntimeIsCurrent, discordGatewayRuntimeIsCurrent, runtimeCallbackRecord, runtimeCallbackEndpoint, applyProviderLifecycleEffect, runtimeGeneration, discordGatewayEventBarrier: (event) => options.discordGatewayEventBarrier?.(event) ?? Promise.resolve() }); const { handlePhotonFailure, handlePhotonEvent } = createPhotonEvents(db, { runtimeCallbackRecord, runtimeCallbackEndpoint, invalidateRuntime, ensurePrincipal, requireCurrentExternalActionAuthorization, isExternalActionAuthorizationChange, scheduleMessageProcessing, processPendingPublications, questionResponses, redactError, renewDiscordGatewayOwnershipForMessageAdmission, handleSdkMessage }); const { handleTelegramGenerationStopped, reserveTelegramPublicationDraft } = createTelegramPublicationDrafts(db, { runtimeCallbackRecord, runtimeCallbackEndpoint, runtimeFor, runtime, runtimeContexts, credentialFingerprint, authorizeRetainedChatSourcePublication }); const { recordSlackFileUploadReceipt, stageSlackFilePublicationSuccess, processPendingSlackFileUploadReceipts, nextSlackReceiptAt } = createSlackFileReceipts(db, { endpointRecord, withCredentialMutationLease, runtimeCallbackEndpoint, nonDirectDestinationAllowed, runtimeFor, isExplicitOperatorPublication, recordExactOutboundPublicationLink }); @@ -7143,288 +4986,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .limit(1).then((rows) => rows[0] ?? null); } - async function setupTestStatus(endpointId: string, userId: string) { - const record = await endpointRecord(endpointId); - if (!record) throw notFound("Chat endpoint not found"); - const startedAt = record.endpoint.setup.testStartedAt; - if (!startedAt) return { messageReceivedAt: null }; - const links = await db.select({ principalId: chatIdentityLinks.principalId }).from(chatIdentityLinks).where(and( - eq(chatIdentityLinks.companyId, record.endpoint.companyId), eq(chatIdentityLinks.endpointId, endpointId), - eq(chatIdentityLinks.paperclipUserId, userId), eq(chatIdentityLinks.status, "linked"), - )); - if (!links.length) return { messageReceivedAt: null }; - const principalIds = links.map((link) => link.principalId); - const [delivery, command] = await Promise.all([ - db.select({ at: chatDeliveries.createdAt }).from(chatDeliveries).where(and( - eq(chatDeliveries.companyId, record.endpoint.companyId), eq(chatDeliveries.endpointId, endpointId), - inArray(chatDeliveries.principalId, principalIds), gte(chatDeliveries.createdAt, new Date(startedAt)), - inArray(chatDeliveries.eventKind, ["message", "mention"]), - )).orderBy(asc(chatDeliveries.createdAt)).limit(1).then((rows) => rows[0]), - db.select({ at: chatActions.createdAt }).from(chatActions).where(and( - eq(chatActions.companyId, record.endpoint.companyId), eq(chatActions.endpointId, endpointId), - inArray(chatActions.principalId, principalIds), eq(chatActions.kind, "slash_task_start"), gte(chatActions.createdAt, new Date(startedAt)), - )).orderBy(asc(chatActions.createdAt)).limit(1).then((rows) => rows[0]), - ]); - const at = [delivery?.at, command?.at].filter((value): value is Date => Boolean(value)).sort((a, b) => a.getTime() - b.getTime())[0]; - return { messageReceivedAt: at?.toISOString() ?? null }; - } - - async function test(endpointId: string, finishOptions?: { optionalSlackTestForUser: string }) { - const initial = await endpointRecord(endpointId); - if (!initial) throw notFound("Chat endpoint not found"); - return withCredentialMutationLease( - initial.endpoint, - async (credentialLease) => { - // Reload only after owning the same lane as reconnect, pause, removal, - // and credential rotation. Qualification and activation must describe - // one exact provider runtime, never a setup snapshot that a concurrent - // reconnect has already superseded. - const record = await endpointRecord(endpointId); - if (!record) throw notFound("Chat endpoint not found"); - const endpoint = record.endpoint; - if (endpoint.status !== "verifying" || endpoint.setup.step !== "test") { - throw conflict("This connection is not waiting for a setup test", { - code: "chat_endpoint_not_testing", - }); - } - if (endpoint.provider === "github" && endpoint.setup.github) { - const verified = await githubChatManagementService(db, fetchImpl).verification(endpoint.id); - if (!verified.ready && !(finishOptions?.optionalSlackTestForUser && verified.connectionReady)) throw conflict("Finish verifying the App, repositories, and assigned agent's tools before activating this bot", { checks: verified.checks }); - } - const testStartedAtValue = endpoint.setup.testStartedAt; - const testStartedAt = testStartedAtValue - ? new Date(testStartedAtValue) - : null; - const optionalSlackTest = Boolean(finishOptions?.optionalSlackTestForUser); - if (optionalSlackTest) { - if (!["slack", "github"].includes(endpoint.provider) || !endpoint.setup.webhookVerifiedAt || !endpoint.providerAccountId) { - throw conflict("Verify the connection before finishing setup"); - } - const linked = await findAuthorizedIdentityLink(endpoint, finishOptions!.optionalSlackTestForUser); - if (!linked) throw forbidden("Connect your account before finishing setup"); - } - if (!optionalSlackTest) { - if ( - !endpoint.lastEventAt || - !testStartedAtValue || - !testStartedAt || - Number.isNaN(testStartedAt.getTime()) || - endpoint.lastEventAt < testStartedAt - ) { - throw conflict( - "Send the test message in the provider before completing setup", - { - code: "chat_test_message_missing", - }, - ); - } - const requiredTrigger = - ["telegram", "imessage-photon"].includes(endpoint.provider) - ? "direct_message" - : "subscribed_message"; - const qualifyingDelivery = await db - .select({ - id: chatDeliveries.id, - conversationId: chatDeliveries.conversationId, - processedAt: chatDeliveries.processedAt, - }) - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.companyId, endpoint.companyId), - eq(chatDeliveries.endpointId, endpoint.id), - eq(chatDeliveries.state, "processed"), - gte(chatDeliveries.processedAt, testStartedAt), - sql`${chatDeliveries.normalizedEvent}->>'trigger' = ${requiredTrigger}`, - ), - ) - .orderBy(desc(chatDeliveries.processedAt)) - .limit(1) - .then((rows) => rows[0] ?? null); - if ( - !qualifyingDelivery?.conversationId || - !qualifyingDelivery.processedAt - ) { - throw conflict( - ["telegram", "imessage-photon"].includes(endpoint.provider) - ? "Send the test direct message before completing setup" - : "Reply once without mentioning the agent before completing setup", - { code: "chat_test_follow_up_missing" }, - ); - } - const finalPublication = await db - .select({ - commentId: chatPublications.commentId, - payload: chatPublications.payload, - }) - .from(chatPublications) - .innerJoin( - issueComments, - and( - eq(issueComments.id, chatPublications.commentId), - eq(issueComments.companyId, chatPublications.companyId), - eq(issueComments.issueId, chatPublications.issueId), - eq(issueComments.authorType, "agent"), - eq(issueComments.authorAgentId, endpoint.assignedAgentId), - ), - ) - .innerJoin( - chatMessageLinks, - and( - eq(chatMessageLinks.companyId, chatPublications.companyId), - eq(chatMessageLinks.endpointId, chatPublications.endpointId), - eq( - chatMessageLinks.conversationId, - chatPublications.conversationId, - ), - eq(chatMessageLinks.deliveryId, qualifyingDelivery.id), - eq(chatMessageLinks.direction, "inbound"), - isNotNull(chatMessageLinks.commentId), - ), - ) - .innerJoin( - heartbeatRuns, - and( - eq(heartbeatRuns.id, issueComments.createdByRunId), - eq(heartbeatRuns.companyId, chatPublications.companyId), - eq(heartbeatRuns.agentId, endpoint.assignedAgentId), - eq(heartbeatRuns.status, "succeeded"), - eq( - sql`${heartbeatRuns.contextSnapshot} ->> 'issueId'`, - sql`${chatPublications.issueId}::text`, - ), - or( - sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`, - sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot} ->> 'commentId'`, - sql`coalesce(${heartbeatRuns.contextSnapshot} -> 'wakeCommentIds', '[]'::jsonb) ? ${chatMessageLinks.commentId}::text`, - ), - ), - ) - .where( - and( - eq(chatPublications.companyId, endpoint.companyId), - eq(chatPublications.endpointId, endpoint.id), - eq( - chatPublications.conversationId, - qualifyingDelivery.conversationId, - ), - eq(chatPublications.state, "published"), - gte(chatPublications.publishedAt, qualifyingDelivery.processedAt), - ), - ) - .orderBy(desc(chatPublications.publishedAt)) - .then((rows) => - rows.find( - (row) => - row.payload.interactionId === undefined && - row.payload.progressState === undefined && - row.commentId !== null, - ), - ); - let githubToolReply = false; - if (!finalPublication && endpoint.provider === "github") { - const setupRuns = await db.select({ id: heartbeatRuns.id, issueId: chatConversations.issueId }) - .from(heartbeatRuns) - .innerJoin(chatMessageLinks, and( - eq(chatMessageLinks.companyId, endpoint.companyId), - eq(chatMessageLinks.endpointId, endpoint.id), - eq(chatMessageLinks.deliveryId, qualifyingDelivery.id), - eq(chatMessageLinks.direction, "inbound"), - or( - sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot}->>'wakeCommentId'`, - sql`coalesce(${heartbeatRuns.contextSnapshot}->'wakeCommentIds', '[]'::jsonb) ? ${chatMessageLinks.commentId}::text`, - ), - )) - .innerJoin(chatConversations, and( - eq(chatConversations.id, qualifyingDelivery.conversationId), - eq(chatConversations.id, chatMessageLinks.conversationId), - eq(chatConversations.companyId, endpoint.companyId), - sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${chatConversations.issueId}::text`, - )) - .where(and(eq(heartbeatRuns.companyId, endpoint.companyId), - eq(heartbeatRuns.agentId, endpoint.assignedAgentId), eq(heartbeatRuns.status, "succeeded"))) - .orderBy(desc(heartbeatRuns.createdAt)).limit(20); - for (const run of setupRuns) { - if (await githubRunReplyState(db, { companyId: endpoint.companyId, endpointId: endpoint.id, - issueId: run.issueId, runId: run.id }) === "confirmed") { - githubToolReply = true; - break; - } - } - } - if (!finalPublication && !githubToolReply) { - throw conflict( - "Wait for the Paperclip agent to reply to the setup turn before completing setup", - { - code: "chat_test_round_trip_incomplete", - }, - ); - } - } - await options.setupTestActivationBarrier?.(); - const expectedGeneration = runtimeGeneration(endpoint.setup); - await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - if (optionalSlackTest) { - const linked = await tx.select({ principalId: chatIdentityLinks.principalId }).from(chatIdentityLinks).where(and( - eq(chatIdentityLinks.endpointId, endpoint.id), eq(chatIdentityLinks.paperclipUserId, finishOptions!.optionalSlackTestForUser), eq(chatIdentityLinks.status, "linked"), - )).limit(1).then((rows) => rows[0]); - if (!linked || !(await lockCurrentPrincipalAuthorization(tx, endpoint, linked.principalId)).allowed) throw forbidden("Your connected account is no longer authorized"); - } - await notifyChatEndpointWork(tx); - const [activated] = await tx - .update(chatEndpoints) - .set({ - status: "active", - setup: { - ...endpoint.setup, - step: "complete", - testStartedAt: null, - testSkipped: optionalSlackTest, - }, - healthMessage: optionalSlackTest ? "Connected; conversation test optional" : "Connected", - activatedAt: endpoint.activatedAt ?? new Date(), - updatedAt: new Date(), - }) - .where( - and( - eq(chatEndpoints.id, endpointId), - eq(chatEndpoints.status, "verifying"), - sql`${chatEndpoints.setup}->>'step' = 'test'`, - sql`coalesce((${chatEndpoints.setup}->>'runtimeGeneration')::integer, 0) = ${expectedGeneration}`, - sql`${chatEndpoints.setup}->>'testStartedAt' = ${testStartedAtValue}`, - ), - ) - .returning({ id: chatEndpoints.id }); - if (!activated) { - throw conflict("This setup test was superseded by a reconnect", { - code: "chat_endpoint_test_superseded", - }); - } - await tx - .update(toolConnections) - .set({ - status: "active", - enabled: true, - healthStatus: "healthy", - healthMessage: optionalSlackTest ? "Connected; conversation test optional" : "Connected", - lastError: null, - healthCheckedAt: new Date(), - updatedAt: new Date(), - }) - .where(eq(toolConnections.id, endpoint.connectionId)); - if (optionalSlackTest) await logActivity(tx as unknown as Db, { - companyId: endpoint.companyId, actorType: "user", actorId: finishOptions!.optionalSlackTestForUser, - action: "chat.setup_completed", entityType: "chat_endpoint", entityId: endpointId, - details: { conversationTestOptional: true }, - }); - await credentialLease.assertOwned(tx); - }); - return get(endpointId); - }, - ); - } - async function ensurePrincipal( endpoint: EndpointRow, author: Author, @@ -7795,595 +5356,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return resource; } - async function ingestAttachments(input: { - endpoint: EndpointRow; - endpointRuntime: ChatEndpointRuntime; - deliveryId: string; - issueId: string; - issueCommentId: string; - attachments: Attachment[]; - attachmentLimitOmissions?: number; - unavailableReferenceCount?: number; - actorUserId: string | null; - }): Promise<{ - storedIds: string[]; - omissionReasons: Record; - }> { - const omissionReasons: Record = {}; - const omit = (reason: string, count = 1) => { - omissionReasons[reason] = (omissionReasons[reason] ?? 0) + count; - }; - const boundedAttachments = input.attachments.slice(0, 20); - const photonDerivatives = new Map(); - const originalPhotonAttachments = new Map(); - if ( - input.endpoint.provider === "microsoft-teams" && - input.unavailableReferenceCount - ) { - // A channel/group reference is not a download capability. Still tell - // the exact current-turn consumer that those bytes are unavailable, - // including when there are no ingestible attachments at all. - omit("download_unavailable", input.unavailableReferenceCount); - } - if ( - ["github", "imessage-photon"].includes(input.endpoint.provider) && - input.attachmentLimitOmissions - ) { - omit("attachment_limit", input.attachmentLimitOmissions); - } - if (input.attachments.length > boundedAttachments.length) { - omit( - "attachment_limit", - input.attachments.length - boundedAttachments.length, - ); - } - if (!options.storage) { - if (boundedAttachments.length) - omit("storage_unavailable", boundedAttachments.length); - return { storedIds: [], omissionReasons }; - } - const existingByFingerprint = new Map(); - const existingAttachments = await db - .select({ - id: issueAttachments.id, - byteSize: assets.byteSize, - contentType: assets.contentType, - originalFilename: assets.originalFilename, - sha256: assets.sha256, - }) - .from(issueAttachments) - .innerJoin( - assets, - and( - eq(assets.companyId, issueAttachments.companyId), - eq(assets.id, issueAttachments.assetId), - ), - ) - .where( - and( - eq(issueAttachments.companyId, input.endpoint.companyId), - eq(issueAttachments.issueId, input.issueId), - eq(issueAttachments.issueCommentId, input.issueCommentId), - ), - ); - for (const existing of existingAttachments) { - const fingerprint = JSON.stringify([ - existing.sha256, - existing.byteSize, - existing.contentType, - existing.originalFilename, - ]); - const ids = existingByFingerprint.get(fingerprint) ?? []; - ids.push(existing.id); - existingByFingerprint.set(fingerprint, ids); - } - const storedIds: string[] = []; - const withCurrentAttachmentAuthorization = async ( - work: (tx: DbTransaction) => Promise, - enforceUnchangedSource = false, - telegramMedia?: Attachment, - ): Promise => { - for (let attempt = 0; ; attempt++) { - try { - return await db.transaction(async (tx) => { - const action = await tx - .select() - .from(chatActions) - .where( - and( - eq(chatActions.companyId, input.endpoint.companyId), - eq(chatActions.endpointId, input.endpoint.id), - eq(chatActions.deliveryId, input.deliveryId), - eq(chatActions.kind, "inbound_wakeup"), - ), - ) - .limit(1) - .then((rows) => rows[0]); - if ( - !action || - !["preparing", "issued", "processing", "processed"].includes( - action.status, - ) || - action.payload.issueId !== input.issueId || - action.payload.commentId !== input.issueCommentId - ) - throw forbidden( - "The attachment's current chat admission is unavailable", - { code: "chat_action_authorization_changed" }, - ); - const current = await authorizeInboundWakeup(tx, action); - // Delivery settlement/replay can update these receipts without - // taking the issue lock. Lock in delivery -> action order only - // after policy authorization; NOWAIT breaks cross-worker lock - // inversions and the outer loop retries the whole transaction. - const lockedDelivery = await tx - .select() - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.companyId, input.endpoint.companyId), - eq(chatDeliveries.id, input.deliveryId), - ), - ) - .for("update", { noWait: true }) - .limit(1) - .then((rows) => rows[0]); - const lockedAction = await tx - .select() - .from(chatActions) - .where( - and( - eq(chatActions.companyId, input.endpoint.companyId), - eq(chatActions.id, action.id), - ), - ) - .for("update", { noWait: true }) - .limit(1) - .then((rows) => rows[0]); - const scopeKeys = [ - "version", - "issueId", - "commentId", - "agentId", - "sessionGeneration", - "requestedByActorType", - "requestedByActorId", - ] as const; - if ( - current.delivery.state !== "processing" || - !lockedDelivery || - !lockedAction || - lockedDelivery.state !== "processing" || - lockedDelivery.endpointId !== action.endpointId || - lockedDelivery.conversationId !== action.conversationId || - lockedDelivery.principalId !== action.principalId || - lockedDelivery.attempts !== current.delivery.attempts || - JSON.stringify(lifecycleRuntimeFence(lockedDelivery)) !== - JSON.stringify(lifecycleRuntimeFence(current.delivery)) || - lockedAction.endpointId !== action.endpointId || - lockedAction.deliveryId !== action.deliveryId || - lockedAction.conversationId !== action.conversationId || - lockedAction.principalId !== action.principalId || - lockedAction.kind !== "inbound_wakeup" || - !["preparing", "issued", "processing", "processed"].includes( - lockedAction.status, - ) || - scopeKeys.some( - (key) => lockedAction.payload[key] !== action.payload[key], - ) - ) - throw forbidden( - "The attachment's current chat admission is unavailable", - { code: "chat_action_authorization_changed" }, - ); - if (enforceUnchangedSource) { - if (telegramMedia) { - const normalized = lockedDelivery.normalizedEvent as { - principal?: { externalId?: string }; - message?: { - providerMessageId?: string; - attachments?: Array<{ recovery?: unknown }>; - }; - }; - const fence = lifecycleRuntimeFence(lockedDelivery); - const descriptor = - input.endpointRuntime.attachmentRecoveryDescriptor( - telegramMedia, - { - threadId: current.conversation.externalThreadId, - messageId: normalized.message?.providerMessageId ?? "", - principalExternalId: normalized.principal?.externalId, - runtimeGeneration: fence?.generation, - credentialFingerprint: fence?.credentialFingerprint, - }, - ); - if ( - descriptor?.locator.kind !== "telegram_media" || - !normalized.message?.attachments?.some((item) => { - const retained = item.recovery as { - version?: unknown; - provider?: unknown; - locator?: Record; - attachment?: Record; - } | null; - return ( - retained?.version === 1 && - retained.provider === "telegram" && - Object.entries(descriptor.locator).every( - ([key, value]) => retained.locator?.[key] === value, - ) && - Object.entries(descriptor.attachment).every( - ([key, value]) => retained.attachment?.[key] === value, - ) - ); - }) - ) - throw forbidden( - "The media's admitted source is unavailable", - { code: "chat_action_authorization_changed" }, - ); - } - const [source] = await tx - .select({ id: issueComments.id }) - .from(issueComments) - .where( - and( - eq(issueComments.companyId, input.endpoint.companyId), - eq(issueComments.issueId, input.issueId), - eq(issueComments.id, input.issueCommentId), - isNull(issueComments.deletedAt), - sql`${issueComments.updatedAt} = ${issueComments.createdAt}`, - ), - ) - .for("share", { noWait: true }); - // Lifecycle admission precedes its conversation drain. Pending - // source changes already invalidate this image download, even - // when the append-only original comment is still untouched. - const [lifecycle] = await tx - .select({ id: chatDeliveries.id }) - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.companyId, input.endpoint.companyId), - eq(chatDeliveries.endpointId, input.endpoint.id), - or( - isNull(chatDeliveries.conversationId), - eq( - chatDeliveries.conversationId, - current.conversation.id, - ), - ), - ne(chatDeliveries.state, "filtered"), - inArray(chatDeliveries.eventKind, [ - "message_updated", - "message_deleted", - "message_restored", - ]), - sql`${chatDeliveries.normalizedEvent}->'runtimeContext' = ${JSON.stringify(lifecycleRuntimeFence(lockedDelivery))}::jsonb`, - sql`${chatDeliveries.normalizedEvent}->'message'->>'targetProviderEventId' = ${lockedDelivery.providerEventId}`, - ), - ) - .limit(1); - if (!source || lifecycle) - throw forbidden( - "The image's admitted source changed before attachment registration", - { - code: "chat_action_authorization_changed", - }, - ); - } - return await work(tx); - }); - } catch (error) { - if (!isExternalChatWaitAuthorizationContention(error) || attempt >= 4) - throw error; - // Retry the complete short transaction; no network/storage work or - // backoff occurs while governance rows are locked. - await new Promise((resolve) => - setTimeout(resolve, 20 * (attempt + 1)), - ); - } - } - }; - // One shared deadline bounds the sequential batch; an expired batch never - // starts another GitHub request. Other providers retain their own policy. - const githubBatchSignal = - input.endpoint.provider === "github" - ? AbortSignal.timeout(GITHUB_ATTACHMENT_BATCH_TIMEOUT_MS) - : undefined; - // This new image-only lane shares one budget across token acquisition and - // downloads. DB/storage commit semantics remain explicit and unchanged. - const teamsInlineBatchSignal = - input.endpoint.provider === "microsoft-teams" - ? AbortSignal.timeout(10_000) - : undefined; - for (let attachment of boundedAttachments) { - const teamsInlineImage = - input.endpoint.provider === "microsoft-teams" && - input.endpointRuntime.attachmentRecoveryDescriptor(attachment)?.locator - .kind === "teams_inline_image"; - const telegramMedia = - input.endpoint.provider === "telegram" && - hasTelegramMediaProvenance(attachment); - const sourceBoundMedia = - teamsInlineImage || - telegramMedia || - input.endpoint.provider === "imessage-photon"; - const requireCurrentAttachmentAuthorization = - input.endpoint.provider === "github" || sourceBoundMedia; - try { - if (teamsInlineImage && teamsInlineBatchSignal?.aborted) { - omit("download_unavailable"); - continue; - } - // GitHub's anonymized upload URLs carry no trustworthy MIME metadata. - // Resolve public bytes only here, after durable comment admission, and - // then apply the same storage/type policy as every native attachment. - if (input.endpoint.provider === "github") { - await withCurrentAttachmentAuthorization(async () => {}); - attachment = await prepareGitHubPublicAttachment( - attachment, - githubBatchSignal, - async (request, signal) => { - await withCurrentAttachmentAuthorization(async () => {}); - return ( - (await input.endpointRuntime.resolveGitHubAttachmentComment?.( - request, - signal, - )) ?? null - ); - }, - ); - } - if ( - requireCurrentAttachmentAuthorization && - input.endpoint.provider !== "github" - ) - await withCurrentAttachmentAuthorization( - async () => {}, - sourceBoundMedia, - telegramMedia ? attachment : undefined, - ); - if ( - attachment.size !== undefined && - attachment.size > MAX_ATTACHMENT_BYTES - ) { - omit("declared_too_large"); - continue; - } - if (!attachment.fetchData) { - omit("download_unavailable"); - continue; - } - const originalFilename = sanitizeFilename(attachment.name); - let contentType = normalizeUploadAttachmentContentType({ - contentType: normalizeContentType( - attachment.mimeType ?? "application/octet-stream", - ), - originalFilename, - isAllowedContentType, - }); - // Reject a provider-declared type before allocating the downloaded - // payload. The actual byte length is checked again after the adapter's - // bounded fetch contract resolves. - // A filename may infer an Office MIME for ordinary documents, but it - // must not bypass byte identification for native media without MIME. - const identifyTelegram = - telegramMedia && - telegramMediaNeedsIdentification(attachment) && - normalizeContentType( - attachment.mimeType ?? "application/octet-stream", - ) === "application/octet-stream"; - if (!isAllowedContentType(contentType) && !identifyTelegram) { - omit("unsupported_type"); - continue; - } - const fetched = teamsInlineImage - ? await input.endpointRuntime.fetchTeamsInlineImage( - attachment, - teamsInlineBatchSignal!, - ) - : await attachment.fetchData(); - const body = Buffer.isBuffer(fetched) ? fetched : Buffer.from(fetched); - if (body.length === 0) { - omit("empty_download"); - continue; - } - if (body.length > MAX_ATTACHMENT_BYTES) { - omit("downloaded_too_large"); - continue; - } - if (identifyTelegram) { - contentType = - identifyTelegramMedia(attachment, body) ?? - "application/octet-stream"; - if (!isAllowedContentType(contentType)) { - omit("unsupported_type"); - continue; - } - } - const photonSourceHash = createHash("sha256") - .update(body) - .digest("hex"); - if (input.endpoint.provider === "imessage-photon") { - await validatePhotonImage(body, contentType); - const companion = takePhotonCompanion(body); - if (companion?.unavailable) omit("companion_unavailable"); - else if (companion) { - const video: Attachment = { type: "video", name: companion.fileName, mimeType: companion.mimeType, size: companion.data.length, fetchData: async () => companion.data }; - photonDerivatives.set(video, { source: attachment, sourceHash: photonSourceHash, kind: "live_photo_video" }); - boundedAttachments.push(video); - } - if ( - HEIF_CONTENT_TYPES.has(contentType) && - isAllowedContentType("image/jpeg") - ) { - try { - const preview = await photonHeifPreview(body); - const derivative: Attachment = { - type: "image", - name: `${originalFilename} (JPEG preview).jpg`, - mimeType: "image/jpeg", - size: preview.length, - fetchData: async () => preview, - }; - photonDerivatives.set(derivative, { source: attachment, sourceHash: photonSourceHash, kind: "heif_jpeg_preview" }); - boundedAttachments.push(derivative); - } catch { - omit("preview_unavailable"); - } - } - } - const fingerprint = JSON.stringify([ - photonSourceHash, - body.length, - contentType, - originalFilename, - ]); - const existingIds = existingByFingerprint.get(fingerprint); - const existingId = existingIds?.shift(); - if (requireCurrentAttachmentAuthorization) { - // Revoke during either API resolution or the anonymous byte download - // must prevent storage and attachment registration for that input. - await withCurrentAttachmentAuthorization( - async () => {}, - sourceBoundMedia, - telegramMedia ? attachment : undefined, - ); - } - const registerPhotonProvenance = async (attachmentId: string) => { - if (input.endpoint.provider !== "imessage-photon") return; - const relation = photonDerivatives.get(attachment); - if (!relation) { - originalPhotonAttachments.set(attachment, attachmentId); - return; - } - const originalAttachmentId = - originalPhotonAttachments.get(relation.source); - if (!originalAttachmentId) - throw new Error("Photon related attachment source is missing"); - await db - .insert(chatActions) - .values({ - companyId: input.endpoint.companyId, - endpointId: input.endpoint.id, - deliveryId: input.deliveryId, - kind: relation.kind === "heif_jpeg_preview" ? "attachment_derivative" : "attachment_companion", - providerActionId: `photon-${relation.kind}:${input.deliveryId}:${originalAttachmentId}`, - payload: { - originalAttachmentId, - derivativeAttachmentId: attachmentId, - originalSha256: relation.sourceHash, - derivativeSha256: photonSourceHash, - kind: relation.kind, - }, - status: "processed", - }) - .onConflictDoNothing(); - }; - if (existingId) { - storedIds.push(existingId); - await registerPhotonProvenance(existingId); - continue; - } - const stored = await options.storage.putFile({ - companyId: input.endpoint.companyId, - namespace: `issues/${input.issueId}`, - originalFilename, - contentType, - body, - }); - const registration = { - issueId: input.issueId, - issueCommentId: input.issueCommentId, - provider: stored.provider, - objectKey: stored.objectKey, - contentType: stored.contentType, - byteSize: stored.byteSize, - sha256: stored.sha256, - originalFilename: stored.originalFilename, - createdByUserId: input.actorUserId, - }; - let row; - try { - row = requireCurrentAttachmentAuthorization - ? await withCurrentAttachmentAuthorization( - (tx) => - issueService(tx as unknown as Db).createAttachment( - registration, - ), - sourceBoundMedia, - telegramMedia ? attachment : undefined, - ) - : await issuesSvc.createAttachment(registration); - } catch (error) { - // An explicit authorization denial happens before registration. Do - // not delete on ambiguous database errors which may have committed. - if ( - requireCurrentAttachmentAuthorization && - isExternalActionAuthorizationChange(error) - ) - await options.storage - .deleteObject(input.endpoint.companyId, stored.objectKey) - .catch(() => undefined); - throw error; - } - storedIds.push(row.id); - await registerPhotonProvenance(row.id); - } catch (error) { - if (isExternalActionAuthorizationChange(error)) throw error; - if ( - error instanceof PhotonError && - ["attachment_not_ready", "network", "quota", "credentials"].includes( - error.code, - ) - ) - throw error; - // Use the closed current-input omission vocabulary consumed by native - // prompts; provider-specific diagnostics remain redacted log codes. - omit( - error instanceof GitHubAttachmentUnavailableError - ? "download_unavailable" - : "processing_failed", - ); - // A malformed or unavailable provider attachment must not strand the - // durable text delivery. The rejected file is intentionally omitted; - // the delivery remains auditable through its normalized attachment - // metadata without persisting provider credentials or download URLs. - logger.warn( - { - endpointId: input.endpoint.id, - issueId: input.issueId, - ...(error instanceof GitHubAttachmentUnavailableError - ? { - deliveryId: input.deliveryId, - attachmentDiagnosticCode: error.code, - } - : {}), - attachmentName: sanitizeFilename(attachment.name), - error: redactError(error), - }, - "external chat attachment was rejected", - ); - } - } - return { storedIds, omissionReasons }; - } - - function attachmentOmissionDetail(result: { - omissionReasons: Record; - }): string | null { - const entries = Object.entries(result.omissionReasons).filter( - ([, count]) => count > 0, - ); - const omitted = entries.reduce((total, [, count]) => total + count, 0); - if (!omitted) return null; - const reasons = entries - .map(([reason, count]) => `${reason.replaceAll("_", " ")}: ${count}`) - .join(", "); - return `${omitted} external attachment${omitted === 1 ? " was" : "s were"} omitted (${reasons})`; - } - async function settleTelegramAttachmentOnlyFailure(input: { activeDelivery: DeliveryRow; attachmentResult: { @@ -8491,317 +5463,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return true; } - async function admitDiscordRootMention( - configuredEndpoint: EndpointRow, - event: DiscordRootMentionAdmissionEvent, - context: RuntimeContext, - ): Promise { - if ( - !(await ensureDiscordGatewayRuntimeIsCurrent( - configuredEndpoint.id, - context, - )) - ) { - return false; - } - return withCredentialMutationLease( - configuredEndpoint, - async (credentialLease) => { - const threadId = `discord:${event.guildId}:${event.channelId}:${event.messageId}`; - const providerEventId = `${threadId}:${event.messageId}`; - const allowed = await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - const endpoint = await runtimeCallbackEndpoint( - tx, - configuredEndpoint.id, - context, - ["verifying", "active"], - ); - const existing = await tx - .select({ state: chatDeliveries.state }) - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.endpointId, configuredEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - ), - ) - .then((rows) => rows[0] ?? null); - if ( - existing && - ["processed", "filtered", "failed"].includes(existing.state) - ) - return false; - - let reason: string | null = null; - if (!endpoint || endpoint.providerAccountId !== event.guildId) { - reason = "Connection is not active"; - } - const resource = endpoint - ? await tx - .select() - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.endpointId, endpoint.id), - eq(chatEndpointResources.type, "channel"), - eq( - chatEndpointResources.providerResourceId, - event.channelId, - ), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null) - : null; - const enabledCount = - endpoint?.status === "verifying" - ? await tx - .select({ count: sql`count(*)::int` }) - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.endpointId, endpoint.id), - eq(chatEndpointResources.type, "channel"), - eq(chatEndpointResources.enabled, true), - eq(chatEndpointResources.availability, "available"), - ), - ) - .then((rows) => rows[0]?.count ?? 0) - : 0; - const setupDestination = - endpoint?.status === "verifying" && - enabledCount === 0 && - resource?.availability === "available"; - if ( - !reason && - !( - endpoint?.allowGroupChats && - resource?.availability === "available" && - (resource.enabled || setupDestination) - ) - ) { - reason = "Destination is not enabled in Paperclip"; - } - - if (!reason && endpoint) { - const principal = await tx - .select({ id: chatExternalPrincipals.id }) - .from(chatExternalPrincipals) - .where( - and( - eq(chatExternalPrincipals.companyId, endpoint.companyId), - eq(chatExternalPrincipals.provider, "discord"), - eq(chatExternalPrincipals.providerAccountId, event.guildId), - eq(chatExternalPrincipals.externalId, event.userId), - ), - ) - .then((rows) => rows[0] ?? null); - let allowed: boolean; - let linkedDenied = false; - if (principal) { - const authorization = await lockCurrentPrincipalAuthorization( - tx, - endpoint, - principal.id, - ); - allowed = authorization.allowed; - linkedDenied = authorization.linkedDenied; - } else if (!endpoint.allowUnlinkedPeople) { - allowed = false; - } else if (!endpoint.sponsorUserId) { - allowed = true; - } else { - const sponsor = await tx - .select({ - status: companyMemberships.status, - membershipRole: companyMemberships.membershipRole, - }) - .from(companyMemberships) - .where( - and( - eq(companyMemberships.companyId, endpoint.companyId), - eq(companyMemberships.principalType, "user"), - eq(companyMemberships.principalId, endpoint.sponsorUserId), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - allowed = - sponsor?.status === "active" && - sponsor.membershipRole !== "viewer"; - } - if (!allowed) { - reason = linkedDenied - ? "Linked Paperclip account is not currently permitted" - : endpoint.allowUnlinkedPeople - ? "Endpoint sponsor can no longer authorize external guests" - : "External identity must be linked to a Paperclip account"; - } - } - if (!reason) { - await credentialLease.assertOwned(tx); - return true; - } - - const filteredAt = new Date(); - const normalizedEvent = { - providerEventId, - kind: "mention", - trigger: "mention", - resource: { - type: "channel", - providerResourceId: event.channelId, - }, - conversation: { externalThreadId: threadId }, - message: { providerMessageId: event.messageId }, - filtering: { contentRetained: false, providerThreadCreated: false }, - }; - await notifyChatDeliveryWork(tx); - await tx - .insert(chatDeliveries) - .values({ - companyId: configuredEndpoint.companyId, - endpointId: configuredEndpoint.id, - providerEventId, - deduplicationKey: createHash("sha256") - .update(providerEventId) - .digest("hex"), - eventKind: "mention", - normalizedEvent, - state: "filtered", - redactedError: reason, - processedAt: filteredAt, - }) - .onConflictDoUpdate({ - target: [ - chatDeliveries.endpointId, - chatDeliveries.providerEventId, - ], - set: { - normalizedEvent, - principalId: null, - state: "filtered", - nextAttemptAt: null, - redactedError: reason, - processedAt: filteredAt, - updatedAt: filteredAt, - }, - }); - await tx - .update(chatEndpoints) - .set({ lastEventAt: filteredAt, updatedAt: filteredAt }) - .where(eq(chatEndpoints.id, configuredEndpoint.id)); - await credentialLease.assertOwned(tx); - return false; - }); - if (!allowed || !event.message || !context.endpointRuntime) { - return false; - } - const thread = context.endpointRuntime.thread(event.threadId); - await processMessage( - configuredEndpoint, - thread, - event.message, - "mention", - true, - null, - context, - undefined, - null, - true, - undefined, - true, - ); - const staged = await db - .select({ state: chatDeliveries.state }) - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.endpointId, configuredEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - ), - ) - .then((rows) => rows[0] ?? null); - if ( - !staged || - !["received", "retry", "processing"].includes(staged.state) - ) { - return false; - } - - await options.discordRootThreadTransportBarrier?.(); - await credentialLease.assertOwned(); - if ( - !(await ensureDiscordGatewayRuntimeIsCurrent( - configuredEndpoint.id, - context, - )) - ) { - return false; - } - try { - await context.endpointRuntime.ensureDiscordRootThread({ - channelId: event.channelId, - content: event.message.text, - messageId: event.messageId, - }); - } catch (error) { - // The durable provisional receipt owns recovery. Do not let a - // transient REST failure escape the Gateway event handler or ask the - // adapter to create the same thread outside Paperclip's lifecycle - // fence. - logger.warn( - { - endpointId: configuredEndpoint.id, - error: redactError(error), - providerEventId, - }, - "Discord root thread creation will be retried from the durable receipt", - ); - return false; - } - const promoted = await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - const current = await runtimeCallbackEndpoint( - tx, - configuredEndpoint.id, - context, - ["verifying", "active"], - ); - if (!current) return false; - await notifyChatDeliveryWork(tx); - const [row] = await tx - .update(chatDeliveries) - .set({ - state: "received", - normalizedEvent: sql`${chatDeliveries.normalizedEvent} - 'providerThreadPending'`, - nextAttemptAt: null, - redactedError: null, - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.endpointId, configuredEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - eq(chatDeliveries.state, "retry"), - sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, - ), - ) - .returning({ id: chatDeliveries.id }); - await credentialLease.assertOwned(tx); - return Boolean(row); - }); - if (promoted) - scheduleConversationDrain(configuredEndpoint.id, threadId); - // Paperclip created the provider thread inside its credential/reach - // lease. Returning false prevents the adapter from creating it again; - // the durable drain owns the remaining task mutation. - return false; - }, - ); - } - async function stageInboundWakeup( tx: DbOrTransaction, input: { @@ -12161,197 +8822,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); } - function discordRootThreadPending( - delivery: Pick, - ): boolean { - return ( - Boolean(delivery.normalizedEvent) && - typeof delivery.normalizedEvent === "object" && - !Array.isArray(delivery.normalizedEvent) && - (delivery.normalizedEvent as { providerThreadPending?: unknown }) - .providerThreadPending === true - ); - } - - async function reconcileDiscordRootThread( - endpoint: EndpointRow, - delivery: DeliveryRow, - threadId: string, - ): Promise { - try { - const normalized = delivery.normalizedEvent as { - message?: { providerMessageId?: unknown; text?: unknown }; - resource?: { providerResourceId?: unknown }; - }; - const discordThreadParts = threadId.split(":"); - const channelId = - discordThreadParts[0] === "discord" && discordThreadParts.length >= 4 - ? discordThreadParts[2] - : normalized.resource?.providerResourceId; - const messageId = normalized.message?.providerMessageId; - if (typeof channelId !== "string" || typeof messageId !== "string") { - throw new DiscordAdapterCompatibilityError( - "the provisional root receipt is missing its channel or message id", - ); - } - const runtimeFence = lifecycleRuntimeFence(delivery); - if (!runtimeFence) { - throw new DiscordAdapterCompatibilityError( - "the provisional root receipt is missing its runtime fence", - ); - } - return await withCredentialMutationLease( - endpoint, - async (credentialLease) => { - const currentEndpoint = await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - const current = await runtimeCallbackEndpoint( - tx, - endpoint.id, - runtimeFence, - ["verifying", "active"], - ); - await credentialLease.assertOwned(tx); - return current; - }); - if (!currentEndpoint) { - throw conflict( - "Discord root thread credentials or connection state changed before recovery", - { code: "chat_endpoint_runtime_superseded" }, - ); - } - const endpointRuntime = await runtimeFor(currentEndpoint); - await credentialLease.assertOwned(); - await endpointRuntime.ensureDiscordRootThread({ - channelId, - messageId, - content: - typeof normalized.message?.text === "string" - ? normalized.message.text - : "Paperclip task", - }); - return await db.transaction(async (tx) => { - await credentialLease.assertOwned(tx); - const current = await runtimeCallbackEndpoint( - tx, - endpoint.id, - runtimeFence, - ["verifying", "active"], - ); - if (!current) return false; - await notifyChatDeliveryWork(tx); - const [promoted] = await tx - .update(chatDeliveries) - .set({ - state: "received", - normalizedEvent: sql`${chatDeliveries.normalizedEvent} - 'providerThreadPending'`, - nextAttemptAt: null, - redactedError: null, - updatedAt: new Date(), - }) - .where( - and( - eq(chatDeliveries.id, delivery.id), - eq(chatDeliveries.state, "retry"), - sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, - ), - ) - .returning({ id: chatDeliveries.id }); - await credentialLease.assertOwned(tx); - return Boolean(promoted); - }); - }, - ); - } catch (error) { - const disposition = classifyChatPublicationError( - error, - delivery.attempts + 1, - ); - const terminal = - disposition.kind === "resource_unavailable" || - disposition.kind === "failed"; - const retryAfterMs = - disposition.kind === "retry" - ? disposition.retryAfterMs - : DISCORD_ROOT_THREAD_CONFIRMATION_DELAY_MS; - const now = new Date(); - await db.transaction(async (tx) => { - if (disposition.kind === "resource_unavailable") { - const fence = lifecycleRuntimeFence(delivery); - const normalized = delivery.normalizedEvent as { - resource?: { providerResourceId?: unknown }; - }; - const providerResourceId = normalized.resource?.providerResourceId; - const current = fence - ? await runtimeCallbackEndpoint(tx, endpoint.id, fence, [ - "verifying", - "active", - "attention", - ]) - : null; - const resource = - current && typeof providerResourceId === "string" - ? await tx - .select({ id: chatEndpointResources.id }) - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.companyId, current.companyId), - eq(chatEndpointResources.endpointId, current.id), - eq( - chatEndpointResources.providerResourceId, - providerResourceId, - ), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null) - : null; - if (resource) { - await tx - .update(chatEndpointResources) - .set({ availability: "unavailable", updatedAt: now }) - .where(eq(chatEndpointResources.id, resource.id)); - await tx - .update(chatConversations) - .set({ state: "unavailable", updatedAt: now }) - .where( - and( - eq(chatConversations.companyId, current!.companyId), - eq(chatConversations.endpointId, current!.id), - eq(chatConversations.resourceId, resource.id), - inArray(chatConversations.state, ["active", "waiting"]), - ), - ); - } - } - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set({ - state: terminal ? "filtered" : "retry", - attempts: delivery.attempts + 1, - nextAttemptAt: terminal - ? null - : new Date(now.getTime() + retryAfterMs), - processedAt: terminal ? now : null, - redactedError: terminal - ? "Discord thread creation was not confirmed" - : redactError(error), - updatedAt: now, - }) - .where( - and( - eq(chatDeliveries.id, delivery.id), - eq(chatDeliveries.state, "retry"), - sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, - ), - ); - }); - return false; - } - } - function normalizedDeliveryThreadId(delivery: Pick): string | null { const normalized = delivery.normalizedEvent as { conversation?: { externalThreadId?: unknown }; @@ -14163,479 +10633,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); } - function reactionConversationThreadCondition( - provider: ChatProvider, - threadId: string, - ): SQL | undefined { - const exact = externalThreadIdentityCondition( - sql`${chatConversations.externalThreadId}`, - threadId, - ); - const slackDmChannel = - provider === "slack" ? slackThreadChannelId(threadId) : null; - return slackDmChannel && /^D[A-Z0-9-]*$/i.test(slackDmChannel) - ? or( - exact, - and( - eq(chatConversations.isDirectMessage, true), - eq(chatConversations.externalThreadId, `slack:${slackDmChannel}:`), - ), - ) - : exact; - } - - async function reactionConversationForMessage( - database: DbOrTransaction, - endpoint: EndpointRow, - threadId: string, - providerMessageId: string, - direction?: "outbound", - ): Promise { - return database - .select({ conversation: chatConversations }) - .from(chatMessageLinks) - .innerJoin( - chatConversations, - and( - eq(chatConversations.companyId, chatMessageLinks.companyId), - eq(chatConversations.endpointId, chatMessageLinks.endpointId), - eq(chatConversations.id, chatMessageLinks.conversationId), - ), - ) - .where( - and( - eq(chatMessageLinks.companyId, endpoint.companyId), - eq(chatMessageLinks.endpointId, endpoint.id), - eq(chatMessageLinks.providerMessageId, providerMessageId), - direction ? eq(chatMessageLinks.direction, direction) : undefined, - reactionConversationThreadCondition(endpoint.provider, threadId), - ), - ) - .orderBy(desc(chatConversations.sessionGeneration)) - .limit(1) - .then((rows) => rows[0]?.conversation ?? null); - } - - async function lockReactionConversationForMessage( - tx: DbTransaction, - endpoint: EndpointRow, - threadId: string, - providerMessageId: string, - direction?: "outbound", - ): Promise { - const candidate = await reactionConversationForMessage( - tx, - endpoint, - threadId, - providerMessageId, - direction, - ); - if (!candidate) return null; - const current = await tx - .select() - .from(chatConversations) - .where( - and( - eq(chatConversations.companyId, endpoint.companyId), - eq(chatConversations.endpointId, endpoint.id), - eq(chatConversations.id, candidate.id), - ), - ) - // Publication settlement inserts the outbound message link before it - // updates the endpoint. NO KEY UPDATE serializes state changes while - // remaining compatible with that link's conversation FK KEY SHARE. - .for("no key update") - .then((rows) => rows[0] ?? null); - if ( - !current || - !(current.isDirectMessage - ? ["active", "waiting", "completed"].includes(current.state) - : ["active", "waiting"].includes(current.state)) - ) { - return null; - } - const linkStillExists = await tx - .select({ id: chatMessageLinks.id }) - .from(chatMessageLinks) - .where( - and( - eq(chatMessageLinks.companyId, endpoint.companyId), - eq(chatMessageLinks.endpointId, endpoint.id), - eq(chatMessageLinks.conversationId, current.id), - eq(chatMessageLinks.providerMessageId, providerMessageId), - direction ? eq(chatMessageLinks.direction, direction) : undefined, - ), - ) - .limit(1) - .then((rows) => rows.length > 0); - return linkStillExists ? current : null; - } - - async function streamingReactionConversation( - database: DbOrTransaction, - endpoint: EndpointRow, - threadId: string, - ): Promise { - const candidates = await database - .selectDistinct({ - conversation: chatConversations, - sessionGeneration: chatConversations.sessionGeneration, - }) - .from(chatConversations) - .innerJoin( - chatPublications, - and( - eq(chatPublications.companyId, chatConversations.companyId), - eq(chatPublications.endpointId, chatConversations.endpointId), - eq(chatPublications.conversationId, chatConversations.id), - ), - ) - .where( - and( - eq(chatConversations.companyId, endpoint.companyId), - eq(chatConversations.endpointId, endpoint.id), - or( - inArray(chatConversations.state, ["active", "waiting"]), - and( - eq(chatConversations.isDirectMessage, true), - eq(chatConversations.state, "completed"), - ), - ), - reactionConversationThreadCondition(endpoint.provider, threadId), - eq(chatPublications.state, "streaming"), - ), - ) - .orderBy(desc(chatConversations.sessionGeneration)) - .limit(2); - if (candidates.length !== 1) return null; - return candidates[0]!.conversation; - } - - async function lockStreamingReactionConversation( - tx: DbTransaction, - endpoint: EndpointRow, - threadId: string, - ): Promise { - const candidate = await streamingReactionConversation( - tx, - endpoint, - threadId, - ); - if (!candidate) return null; - const current = await tx - .select() - .from(chatConversations) - .where( - and( - eq(chatConversations.companyId, endpoint.companyId), - eq(chatConversations.endpointId, endpoint.id), - eq(chatConversations.id, candidate.id), - or( - inArray(chatConversations.state, ["active", "waiting"]), - and( - eq(chatConversations.isDirectMessage, true), - eq(chatConversations.state, "completed"), - ), - ), - ), - ) - // Keep the same lock strength/order as the exact-link path above so an - // in-flight message-link insert cannot deadlock reaction admission. - .for("no key update") - .then((rows) => rows[0] ?? null); - if (!current) return null; - const stillStreaming = await tx - .select({ id: chatPublications.id }) - .from(chatPublications) - .where( - and( - eq(chatPublications.companyId, endpoint.companyId), - eq(chatPublications.endpointId, endpoint.id), - eq(chatPublications.conversationId, current.id), - eq(chatPublications.state, "streaming"), - ), - ) - .limit(1) - .then((rows) => rows.length > 0); - return stillStreaming ? current : null; - } - - async function authorizeReactionConversation( - tx: DbTransaction, - endpoint: EndpointRow, - conversation: ConversationRow, - principalId: string, - ): Promise { - const resource = - conversation.resourceId && !conversation.isDirectMessage - ? await tx - .select() - .from(chatEndpointResources) - .where( - and( - eq(chatEndpointResources.companyId, endpoint.companyId), - eq(chatEndpointResources.endpointId, endpoint.id), - eq(chatEndpointResources.id, conversation.resourceId), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null) - : null; - const destinationAllowed = conversation.isDirectMessage - ? endpoint.allowDirectMessages - : nonDirectDestinationAllowed(endpoint, resource); - if (!destinationAllowed) return false; - return (await lockCurrentPrincipalAuthorization(tx, endpoint, principalId)) - .allowed; - } - - async function handleReaction( - event: ChatSdkCallbackEvent, - runtimeContext: RuntimeContext, - ) { - const record = await runtimeCallbackRecord( - event.endpointId, - runtimeContext, - ["active"], - ); - if ( - !record || - record.endpoint.status !== "active" || - record.endpoint.provider !== event.provider || - record.endpoint.capabilities.reactions !== true || - event.event.user.isMe || - event.event.user.isBot === true || - event.event.user.isSystem - ) { - return; - } - // A DM can have a newer task generation while this reaction still belongs - // to an older linked message. Resolve its exact lineage before choosing a - // generation; never attach it to the latest task merely sharing the chat. - // A just-sent provider message can be visible before its publication/link - // transaction commits. Only that exact thread's streaming publication is - // enough evidence to stage an otherwise-unbound reaction for replay. - let conversation = await reactionConversationForMessage( - db, - record.endpoint, - event.event.threadId, - event.event.messageId, - ); - await options.reactionLinkPreflightBarrier?.(); - const plausibleStreamingConversation = conversation - ? null - : await streamingReactionConversation( - db, - record.endpoint, - event.event.threadId, - ); - if (!conversation && !plausibleStreamingConversation) { - // Settlement can commit between the first exact-link lookup and the - // streaming-publication lookup. Recheck the durable link before treating - // the callback as an arbitrary unknown message. - conversation = await reactionConversationForMessage( - db, - record.endpoint, - event.event.threadId, - event.event.messageId, - ); - if (!conversation) return; - } - - const principal = await ensurePrincipal( - record.endpoint, - event.event.user, - event.event.raw, - ); - let rawFingerprint: string; - try { - rawFingerprint = createHash("sha256") - .update(JSON.stringify(event.event.raw ?? null)) - .digest("hex") - .slice(0, 24); - } catch { - rawFingerprint = "unavailable"; - } - const eventKind: ChatEventKind = event.event.added - ? "reaction_added" - : "reaction_removed"; - const providerEventId = [ - eventKind, - durableExternalThreadIdentity(event.event.threadId), - event.event.messageId, - stableExternalPrincipalId( - record.endpoint.provider, - event.event.user, - event.event.raw, - ), - event.event.rawEmoji, - rawFingerprint, - ].join(":"); - const normalizedEvent = { - providerEventId, - kind: eventKind, - conversation: { externalThreadId: event.event.threadId }, - message: { providerMessageId: event.event.messageId }, - reaction: { - emoji: event.event.emoji.name, - rawEmoji: event.event.rawEmoji, - added: event.event.added, - }, - runtimeContext: { - generation: runtimeContext.generation, - credentialFingerprint: runtimeContext.credentialFingerprint, - }, - }; - const admitted = await db.transaction(async (tx) => { - const currentEndpoint = await runtimeCallbackEndpoint( - tx, - event.endpointId, - runtimeContext, - ["active"], - ); - if (!currentEndpoint) return false; - const linkedConversation = await lockReactionConversationForMessage( - tx, - currentEndpoint, - event.event.threadId, - event.event.messageId, - ); - const currentConversation = - linkedConversation ?? - (await lockStreamingReactionConversation( - tx, - currentEndpoint, - event.event.threadId, - )); - if ( - !currentConversation || - !(await authorizeReactionConversation( - tx, - currentEndpoint, - currentConversation, - principal.principal.id, - )) - ) { - return false; - } - const now = new Date(); - if (linkedConversation) { - const expiryCutoff = new Date(now.getTime() - REACTION_LINK_MAX_AGE_MS); - const hasOutboundLink = await tx - .select({ id: chatMessageLinks.id }) - .from(chatMessageLinks) - .where( - and( - eq(chatMessageLinks.companyId, currentEndpoint.companyId), - eq(chatMessageLinks.endpointId, currentEndpoint.id), - eq(chatMessageLinks.conversationId, linkedConversation.id), - eq(chatMessageLinks.providerMessageId, event.event.messageId), - eq(chatMessageLinks.direction, "outbound"), - ), - ) - .limit(1) - .then((rows) => rows.length > 0); - await notifyChatDeliveryWork(tx); - const reconciled = hasOutboundLink - ? await tx - .update(chatDeliveries) - .set({ - conversationId: linkedConversation.id, - state: "processed", - attempts: sql`${chatDeliveries.attempts} + 1`, - nextAttemptAt: null, - processedAt: now, - redactedError: null, - updatedAt: now, - }) - .where( - and( - eq(chatDeliveries.endpointId, currentEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - inArray(chatDeliveries.state, ["received", "retry"]), - gt(chatDeliveries.receivedAt, expiryCutoff), - lt(chatDeliveries.attempts, REACTION_LINK_MAX_ATTEMPTS), - sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'generation' = ${String(runtimeContext.generation)}`, - sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'credentialFingerprint' = ${runtimeContext.credentialFingerprint}`, - ), - ) - .returning({ id: chatDeliveries.id }) - : []; - if (reconciled.length > 0) return true; - await notifyChatDeliveryWork(tx); - const expired = await tx - .update(chatDeliveries) - .set({ - state: "filtered", - nextAttemptAt: null, - processedAt: now, - redactedError: - "Reaction target did not become linked before replay expiry", - updatedAt: now, - }) - .where( - and( - eq(chatDeliveries.endpointId, currentEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - inArray(chatDeliveries.state, ["received", "retry"]), - or( - lte(chatDeliveries.receivedAt, expiryCutoff), - gte(chatDeliveries.attempts, REACTION_LINK_MAX_ATTEMPTS), - ), - sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'generation' = ${String(runtimeContext.generation)}`, - sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'credentialFingerprint' = ${runtimeContext.credentialFingerprint}`, - ), - ) - .returning({ id: chatDeliveries.id }); - if (expired.length > 0) return false; - } - await notifyChatDeliveryWork(tx); - const inserted = await tx - .insert(chatDeliveries) - .values({ - companyId: currentEndpoint.companyId, - endpointId: currentEndpoint.id, - conversationId: linkedConversation?.id ?? null, - principalId: principal.principal.id, - providerEventId, - deduplicationKey: createHash("sha256") - .update(providerEventId) - .digest("hex"), - eventKind, - normalizedEvent, - state: linkedConversation ? "processed" : "received", - attempts: linkedConversation ? 1 : 0, - nextAttemptAt: linkedConversation - ? null - : new Date(now.getTime() + REACTION_LINK_RETRY_DELAY_MS), - processedAt: linkedConversation ? now : null, - }) - .onConflictDoNothing() - .returning({ id: chatDeliveries.id }); - if (inserted.length > 0) return true; - return tx - .select({ id: chatDeliveries.id }) - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.endpointId, currentEndpoint.id), - eq(chatDeliveries.providerEventId, providerEventId), - inArray(chatDeliveries.state, ["received", "retry", "processed"]), - ), - ) - .limit(1) - .then((rows) => rows.length > 0); - }); - if (admitted) { - await recordCurrentMicrosoftTeamsRoute( - record.endpoint, - runtimeContext, - event.event.threadId, - event.event.raw, - ); - } - } - async function denyExternalAction( endpoint: EndpointRow, event: ChatSdkCallbackEvent, @@ -18629,56 +14626,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } } - async function verifySlackRequestUrl(endpoint: EndpointRow, request: Request): Promise { - const callback = await inspectSlackCallback(request.clone()); - if (!callback?.isUrlVerification) return null; - const body = await request.clone().text(); - let notifyAccount = false; - // URL verification needs only the signing secret. Do not initialize the - // SDK (which calls Slack auth.test), wait for OAuth, or acquire the creation - // lease: Slack can deliver this check while app setup still owns that lease. - const response = await db.transaction(async tx => { - const [current] = await tx.select().from(chatEndpoints).where(eq(chatEndpoints.id, endpoint.id)).for("no key update"); - if (!current || ["archived", "paused", "revoked"].includes(current.status)) return new Response("ignored", { status: 200 }); - const [connection] = await tx.select({ status: toolConnections.status, refs: toolConnections.credentialSecretRefs }).from(toolConnections) - .where(and(eq(toolConnections.id, current.connectionId), eq(toolConnections.companyId, current.companyId))); - if (!connection || connection.status === "archived") return new Response("ignored", { status: 200 }); - let secret = (await resolveCredentialRefs(current, connection.refs.filter(ref => ref.configPath.replace(/^credentials\./, "") === "signingSecret"), tx)).signingSecret; - if (!secret && isProvisionedSlackSetup(current.setup.slackSetupMethod)) { - const [registration] = await tx.select().from(chatSlackRegistrations) - .where(and(eq(chatSlackRegistrations.endpointId, current.id), eq(chatSlackRegistrations.companyId, current.companyId))); - if (registration?.status !== "removed" && registration?.secretIds.signingSecret) { - secret = await secretService(tx as unknown as Db).resolveSecretValue(current.companyId, registration.secretIds.signingSecret, "latest", { - accessContext: { consumerType: "system", consumerId: `slack-registration:${current.id}`, configPath: "slack_registration.signingSecret", actorType: "system" }, - }); - } - } - if (!secret) return new Response("Slack signing secret is not ready yet", { status: 503 }); - if (!slackRequestSignatureIsValid(request, body, secret)) return new Response("Invalid signature", { status: 401 }); - const workspaceId = slackRequestWorkspaceId(body, request.headers.get("content-type") ?? ""); - if (workspaceId && current.providerAccountId && workspaceId !== current.providerAccountId) return new Response("ignored", { status: 200 }); - const observedAt = new Date(); - const setup = current.setup as InternalSetupState; - const matchesCurrentUrl = slackCallbackMatchesPublicUrl(callback.url, `${getWebhookPublicBaseUrl()}/api/chat-webhooks/${current.publicId}/slack`); - await notifyChatVerificationWork(tx); - await tx.update(chatEndpoints).set({ setup: { - ...setup, - ...(matchesCurrentUrl ? { webhookVerifiedAt: setup.webhookVerifiedAt ?? observedAt.toISOString(), - slackVerificationSigningFingerprint: createHash("sha256").update(secret).digest("hex") } : {}), - slackCallbackSurfaces: { ...setup.slackCallbackSurfaces, events: { url: callback.url, observedAt: observedAt.toISOString() } }, - ...(matchesCurrentUrl && setup.slackAccount?.status === "linked" && !setup.slackAccount.verificationStatus - ? { slackAccount: { ...setup.slackAccount, verificationStatus: "pending" as const } } : {}), - } as InternalSetupState, ...(matchesCurrentUrl ? { healthMessage: "Slack Events Request URL verified" } : {}), updatedAt: observedAt }).where(eq(chatEndpoints.id, current.id)); - if (matchesCurrentUrl && !setup.webhookVerifiedAt) await logActivity(tx as unknown as Db, { companyId: current.companyId, actorType: "system", actorId: "slack-webhook", - action: "chat_endpoint.webhook_verified", entityType: "tool_connection", entityId: current.connectionId, - details: { endpointId: current.id, provider: "slack" } }); - notifyAccount = matchesCurrentUrl && setup.slackAccount?.status === "linked" && !setup.slackAccount.verificationStatus; - return Response.json({ challenge: (JSON.parse(body) as { challenge: string }).challenge }); - }); - if (notifyAccount) scheduleMessageProcessing(() => slackRegistration.notifyVerified(endpoint.id)); - return response; - } - async function handleWebhook( publicId: string, provider: ChatSdkProvider, @@ -19365,334 +15312,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return response; } - function normalizedPendingReaction(delivery: DeliveryRow): { - added: boolean; - emoji: string; - messageId: string; - rawEmoji: string; - runtimeContext: LifecycleRuntimeFence; - threadId: string; - } | null { - if ( - delivery.eventKind !== "reaction_added" && - delivery.eventKind !== "reaction_removed" - ) { - return null; - } - const normalized = delivery.normalizedEvent; - if ( - !normalized || - typeof normalized !== "object" || - Array.isArray(normalized) - ) { - return null; - } - const value = normalized as Record; - const conversation = value.conversation; - const message = value.message; - const reaction = value.reaction; - const runtimeContext = lifecycleRuntimeFence(delivery); - if ( - !conversation || - typeof conversation !== "object" || - Array.isArray(conversation) || - !message || - typeof message !== "object" || - Array.isArray(message) || - !reaction || - typeof reaction !== "object" || - Array.isArray(reaction) || - !runtimeContext - ) { - return null; - } - const threadId = (conversation as Record).externalThreadId; - const messageId = (message as Record).providerMessageId; - const reactionRecord = reaction as Record; - const added = reactionRecord.added; - const emoji = reactionRecord.emoji; - const rawEmoji = reactionRecord.rawEmoji; - return typeof threadId === "string" && - threadId.length > 0 && - typeof messageId === "string" && - messageId.length > 0 && - typeof added === "boolean" && - added === (delivery.eventKind === "reaction_added") && - typeof emoji === "string" && - emoji.length > 0 && - typeof rawEmoji === "string" && - rawEmoji.length > 0 - ? { added, emoji, messageId, rawEmoji, runtimeContext, threadId } - : null; - } - - async function processPendingReactionDelivery( - candidate: DeliveryRow, - ): Promise { - const reaction = normalizedPendingReaction(candidate); - if (!reaction || !candidate.principalId) { - const now = new Date(); - await db.transaction(async (tx) => { - await notifyChatDeliveryWork(tx); - return tx - .update(chatDeliveries) - .set({ - state: "failed", - nextAttemptAt: null, - processedAt: now, - redactedError: "Normalized reaction delivery is incomplete", - updatedAt: now, - }) - .where( - and( - eq(chatDeliveries.id, candidate.id), - inArray(chatDeliveries.state, ["received", "retry"]), - ), - ); - }); - return; - } - const principalId = candidate.principalId; - - await db.transaction(async (tx) => { - const currentEndpoint = await runtimeCallbackEndpoint( - tx, - candidate.endpointId, - reaction.runtimeContext, - ["active"], - ); - let conversation: ConversationRow | null = null; - let disposition: - | { kind: "processed"; conversationId: string } - | { kind: "retry" } - | { kind: "filtered"; reason: string }; - - if (!currentEndpoint) { - disposition = { - kind: "filtered", - reason: "Reaction callback belonged to an unavailable runtime", - }; - } else { - await options.reactionReplayEndpointLockBarrier?.(); - conversation = await lockReactionConversationForMessage( - tx, - currentEndpoint, - reaction.threadId, - reaction.messageId, - "outbound", - ); - if (conversation) { - await options.reactionReplayConversationLockBarrier?.(); - } - if (conversation) { - disposition = (await authorizeReactionConversation( - tx, - currentEndpoint, - conversation, - principalId, - )) - ? { kind: "processed", conversationId: conversation.id } - : { - kind: "filtered", - reason: - "Reaction destination or principal is no longer authorized", - }; - } else { - conversation = await lockStreamingReactionConversation( - tx, - currentEndpoint, - reaction.threadId, - ); - if (conversation) { - await options.reactionReplayConversationLockBarrier?.(); - } - if (!conversation) { - disposition = { - kind: "filtered", - reason: "Reaction target was not linked to a sent message", - }; - } else { - disposition = (await authorizeReactionConversation( - tx, - currentEndpoint, - conversation, - principalId, - )) - ? { kind: "retry" } - : { - kind: "filtered", - reason: - "Reaction destination or principal is no longer authorized", - }; - } - } - } - - // Endpoint, conversation, destination and identity locks are acquired - // before the delivery row so pause/revoke and replay use one lock order. - const locked = await tx - .select() - .from(chatDeliveries) - .where( - and( - eq(chatDeliveries.id, candidate.id), - inArray(chatDeliveries.state, ["received", "retry"]), - ), - ) - .for("update") - .then((rows) => rows[0] ?? null); - // Authorization locks may wait behind a concurrent lifecycle mutation. - // Sample the expiry clock only after this delivery is exclusively held. - const decisionAt = new Date(); - if (!locked || !deliveryReady(locked, decisionAt)) return; - const lockedReaction = normalizedPendingReaction(locked); - if ( - !lockedReaction || - lockedReaction.runtimeContext.generation !== - reaction.runtimeContext.generation || - lockedReaction.runtimeContext.credentialFingerprint !== - reaction.runtimeContext.credentialFingerprint - ) { - disposition = { - kind: "filtered", - reason: "Reaction delivery identity changed before replay", - }; - } - - const nextAttempts = locked.attempts + 1; - const expired = - decisionAt.getTime() - locked.receivedAt.getTime() >= - REACTION_LINK_MAX_AGE_MS; - if (expired || locked.attempts >= REACTION_LINK_MAX_ATTEMPTS) { - disposition = { - kind: "filtered", - reason: "Reaction target did not become linked before replay expiry", - }; - } - if (disposition.kind === "retry" && !expired) { - if (nextAttempts < REACTION_LINK_MAX_ATTEMPTS) { - const delay = Math.min( - REACTION_LINK_RETRY_DELAY_MS * - 2 ** Math.min(Math.max(locked.attempts, 0), 5), - REACTION_LINK_MAX_DELAY_MS, - ); - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set({ - state: "retry", - attempts: nextAttempts, - nextAttemptAt: new Date(decisionAt.getTime() + delay), - redactedError: "Waiting for the sent message link", - updatedAt: decisionAt, - }) - .where( - and( - eq(chatDeliveries.id, locked.id), - eq(chatDeliveries.state, locked.state), - eq(chatDeliveries.attempts, locked.attempts), - ), - ); - return; - } - disposition = { - kind: "filtered", - reason: "Reaction target did not become linked before retry expiry", - }; - } else if (disposition.kind === "retry") { - disposition = { - kind: "filtered", - reason: "Reaction target did not become linked before age expiry", - }; - } - - const processedAt = new Date(); - await notifyChatDeliveryWork(tx); - await tx - .update(chatDeliveries) - .set( - disposition.kind === "processed" - ? { - conversationId: disposition.conversationId, - state: "processed", - attempts: nextAttempts, - nextAttemptAt: null, - processedAt, - redactedError: null, - updatedAt: processedAt, - } - : { - state: "filtered", - attempts: nextAttempts, - nextAttemptAt: null, - processedAt, - redactedError: disposition.reason, - updatedAt: processedAt, - }, - ) - .where( - and( - eq(chatDeliveries.id, locked.id), - eq(chatDeliveries.state, locked.state), - eq(chatDeliveries.attempts, locked.attempts), - ), - ); - }); - } - - async function processPendingReactionDeliveries( - limit: number, - onlyDeliveryId?: string, - ): Promise { - const now = new Date(); - const candidates = await db - .select() - .from(chatDeliveries) - .where( - and( - sql`not exists (select 1 from chat_endpoints e where e.id = ${chatDeliveries.endpointId} and e.provider = 'agentmail')`, - onlyDeliveryId ? eq(chatDeliveries.id, onlyDeliveryId) : undefined, - inArray(chatDeliveries.eventKind, [ - "reaction_added", - "reaction_removed", - ]), - or( - and( - eq(chatDeliveries.state, "received"), - or( - isNull(chatDeliveries.nextAttemptAt), - lte(chatDeliveries.nextAttemptAt, now), - ), - ), - and( - eq(chatDeliveries.state, "retry"), - or( - isNull(chatDeliveries.nextAttemptAt), - lte(chatDeliveries.nextAttemptAt, now), - ), - ), - ), - ), - ) - .orderBy(asc(chatDeliveries.receivedAt), asc(chatDeliveries.id)) - .limit(limit); - for (const candidate of candidates) { - try { - await processPendingReactionDelivery(candidate); - } catch (error) { - logger.warn( - { - endpointId: candidate.endpointId, - deliveryId: candidate.id, - error: redactError(error), - }, - "chat reaction-link reconciliation failed", - ); - } - } - return candidates.length; - } - /** * Reconcile a verified normalized delivery whose original request was * interrupted. Closed, credential-free provider descriptors reconstruct @@ -19926,96 +15545,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return reactionCount + rows.length; } - async function listConversations(endpointId: string) { - const record = await endpointRecord(endpointId); - if (!record) throw notFound("Chat endpoint not found"); - const conversations = await db - .select() - .from(chatConversations) - .where(eq(chatConversations.endpointId, endpointId)) - .orderBy( - desc(chatConversations.lastActivityAt), - desc(chatConversations.createdAt), - ); - if (!conversations.length) return []; - const issueRows = await db - .select({ - id: issues.id, - identifier: issues.identifier, - title: issues.title, - status: issues.status, - updatedAt: issues.updatedAt, - }) - .from(issues) - .where( - inArray( - issues.id, - conversations.map((row) => row.issueId), - ), - ); - const publications = await db - .select() - .from(chatPublications) - .where(eq(chatPublications.endpointId, endpointId)) - .orderBy(desc(chatPublications.createdAt)); - const issueById = new Map(issueRows.map((row) => [row.id, row])); - const lastPublication = new Map< - string, - typeof chatPublications.$inferSelect - >(); - for (const publication of publications) - if (!lastPublication.has(publication.conversationId)) - lastPublication.set(publication.conversationId, publication); - return conversations.map((conversation) => { - const issue = issueById.get(conversation.issueId); - const externalUrl = - record.endpoint.provider === "telegram" && conversation.isDirectMessage - ? chatProviderConversationUrl({ - provider: "telegram", - botUsername: record.endpoint.botUsername, - threadId: conversation.externalThreadId, - providerMessageId: "", - raw: { chat: { type: "private" } }, - }) - : (conversation.providerUrl ?? - (record.endpoint.provider === "slack" - ? chatProviderConversationUrl({ - provider: "slack", - providerAccountId: record.endpoint.providerAccountId, - threadId: conversation.externalThreadId, - providerMessageId: "", - }) - : null)); - return { - id: conversation.id, - companyId: conversation.companyId, - endpointId: conversation.endpointId, - resourceId: conversation.resourceId, - externalLabel: conversation.externalLabel, - externalUrl, - externalConversationId: conversation.externalConversationId, - externalThreadId: conversation.externalThreadId, - sessionGeneration: conversation.sessionGeneration, - issueId: conversation.issueId, - issueIdentifier: issue?.identifier ?? null, - issueTitle: issue?.title ?? null, - isDirectMessage: conversation.isDirectMessage, - state: - record.endpoint.provider !== "imessage-photon" && - (issue?.status === "done" || issue?.status === "cancelled") - ? "completed" - : conversation.state, - lastActivityAt: conversation.lastActivityAt?.toISOString() ?? null, - createdAt: conversation.createdAt.toISOString(), - updatedAt: ( - conversation.lastActivityAt ?? conversation.updatedAt - ).toISOString(), - lastPublicationStatus: - lastPublication.get(conversation.id)?.state ?? null, - }; - }); - } - async function recordGitHubAutomaticSkip( endpoint: EndpointRow, deliveryId: string, event: string, reason: string | undefined, runtimeContext: LifecycleRuntimeFence, ) { @@ -20043,457 +15572,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }); } - async function listActivity(endpointId: string, page?: { limit: number; before?: { createdAt: string; id: string } }) { - const limit = page ? page.limit + 1 : 100; - // Match the millisecond precision of the public timestamp and use a UUID - // tie-breaker so equal timestamps never skip records between pages. - const before = (date: AnyPgColumn, id: AnyPgColumn) => page?.before - ? sql`(date_trunc('milliseconds', ${date}), ${id}) < (${page.before.createdAt}::timestamptz, ${page.before.id}::uuid)` - : undefined; - const recoveryIngress = alias(chatActions, "github_recovery_ingress"); - const [ - deliveries, - publications, - slashActions, - providerEffects, - githubIngressFailures, - slackSessionActions, - githubRecoveries, - githubRecoveryScans, - ] = await Promise.all([ - db - .select() - .from(chatDeliveries) - .where(and(eq(chatDeliveries.endpointId, endpointId), before(chatDeliveries.createdAt, chatDeliveries.id))) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatDeliveries.createdAt})`), desc(chatDeliveries.id)) - .limit(limit), - db - .select() - .from(chatPublications) - .where(and(eq(chatPublications.endpointId, endpointId), before(chatPublications.createdAt, chatPublications.id))) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatPublications.createdAt})`), desc(chatPublications.id)) - .limit(limit), - db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, endpointId), - before(chatActions.createdAt, chatActions.id), - eq(chatActions.kind, "slash_task_start"), - ), - ) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) - .limit(limit), - db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, endpointId), - before(chatActions.createdAt, chatActions.id), - eq(chatActions.kind, "provider_effect"), - eq(chatActions.status, "delivery_unknown"), - ), - ) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) - .limit(limit), - db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, endpointId), - before(chatActions.createdAt, chatActions.id), - eq(chatActions.kind, "github_webhook_ingress"), - eq(chatActions.status, "failed"), - sql`coalesce(${chatActions.result}->>'retryable', 'false') = 'false'`, - ), - ) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) - .limit(limit), - db - .select() - .from(chatActions) - .where( - and( - eq(chatActions.endpointId, endpointId), - before(chatActions.updatedAt, chatActions.id), - inArray(chatActions.kind, [ - "slack_session_sync", - "slack_session_stop", - ]), - ), - ) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.updatedAt})`), desc(chatActions.id)) - .limit(limit), - db - .select({ - action: chatActions, - ingressStatus: recoveryIngress.status, - }) - .from(chatActions) - .leftJoin( - recoveryIngress, - and( - eq(recoveryIngress.companyId, chatActions.companyId), - eq(recoveryIngress.endpointId, chatActions.endpointId), - eq(recoveryIngress.kind, "github_webhook_ingress"), - sql`${recoveryIngress.providerActionId} = 'github_webhook_ingress:' || (${chatActions.payload}#>>'{original,guid}')`, - ), - ) - .where( - and( - eq(chatActions.endpointId, endpointId), - before(chatActions.updatedAt, chatActions.id), - eq(chatActions.kind, "github_webhook_recovery"), - ), - ) - .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.updatedAt})`), desc(chatActions.id)) - .limit(limit), - db - .select() - .from(chatSdkState) - .where( - and( - eq(chatSdkState.endpointId, endpointId), - before(chatSdkState.updatedAt, chatSdkState.id), - eq(chatSdkState.stateKey, GITHUB_RECOVERY_STATE_KEY), - ), - ) - .limit(1), - ]); - // A provider effect can be on another page. Replay safety must still - // consider every unresolved effect associated with these deliveries. - const ambiguousEffects = deliveries.length ? await db.select({ deliveryId: chatActions.deliveryId }) - .from(chatActions).where(and( - eq(chatActions.endpointId, endpointId), - eq(chatActions.kind, "provider_effect"), - eq(chatActions.status, "delivery_unknown"), - inArray(chatActions.deliveryId, deliveries.map((row) => row.id)), - )) : []; - const ambiguousProviderEffectDeliveryIds = new Set(ambiguousEffects.map((row) => row.deliveryId)); - const transferRows = publications.length - ? await db - .select(fileTransferProjectionColumns) - .from(chatTeamsFileTransfers) - .innerJoin( - chatPublications, - and( - eq(chatPublications.id, chatTeamsFileTransfers.publicationId), - eq(chatPublications.companyId, chatTeamsFileTransfers.companyId), - eq( - chatPublications.endpointId, - chatTeamsFileTransfers.endpointId, - ), - eq( - chatPublications.conversationId, - chatTeamsFileTransfers.conversationId, - ), - ), - ) - .where( - and( - eq(chatTeamsFileTransfers.companyId, publications[0]!.companyId), - eq(chatTeamsFileTransfers.endpointId, endpointId), - inArray( - chatTeamsFileTransfers.publicationId, - publications.map((row) => row.id), - ), - ), - ) - : []; - const transfers = new Map( - transferRows.map((row) => [row.publicationId, row]), - ); - const cancellableConflicts = new Map(); - for (const transfer of transferRows) { - if (transfer.phase !== "conflict") continue; - const publication = publications.find( - (row) => row.id === transfer.publicationId, - ); - if (!publication?.conversationId) continue; - if ( - await teamsTransferProtocol().canCancelConflict({ - companyId: publication.companyId, - endpointId, - conversationId: publication.conversationId, - publicationId: publication.id, - version: transfer.version, - }) - ) - cancellableConflicts.set(publication.id, transfer.version); - } - return [ - ...deliveries.map((row) => { - const normalized = - row.normalizedEvent && - typeof row.normalizedEvent === "object" && - !Array.isArray(row.normalizedEvent) - ? (row.normalizedEvent as Record) - : {}; - const deduplication = - normalized.deduplication && - typeof normalized.deduplication === "object" && - !Array.isArray(normalized.deduplication) - ? (normalized.deduplication as Record) - : null; - const duplicateCount = - typeof deduplication?.duplicateCount === "number" - ? deduplication.duplicateCount - : 0; - const label = row.eventKind.replaceAll("_", " "); - const outcome = - row.state === "filtered" - ? "ignored" - : row.state === "processed" - ? "processed" - : row.state; - return { - id: row.id, - kind: "delivery" as const, - status: row.state, - summary: `${label} ${outcome}${duplicateCount ? ` · ${duplicateCount} duplicate${duplicateCount === 1 ? "" : "s"} ignored` : ""}`, - detail: row.redactedError, - createdAt: row.createdAt.toISOString(), - replayable: - row.state === "failed" && - Boolean(row.conversationId) && - !ambiguousProviderEffectDeliveryIds.has(row.id), - resolutionActions: [], - }; - }), - ...publications.map((row) => { - const transfer = transfers.get(row.id); - const projected = projectChatFileTransfer( - publicationSummary(row), - transfer, - ); - const payload = row.payload as Partial; - const summary = payload.progressState - ? `${payload.progressState.replaceAll("_", " ")} update` - : payload.interactionId - ? "Interactive question" - : "Response to external conversation"; - return { - id: row.id, - kind: "publication" as const, - status: projected.state, - summary, - detail: projected.redactedError ?? null, - createdAt: row.createdAt.toISOString(), - replayable: !transfer && row.state === "failed", - ...(projected.fileTransfer - ? { fileTransfer: projected.fileTransfer } - : {}), - resolutionActions: transfer - ? chatFileTransferResolutionActions( - projected, - cancellableConflicts.has(row.id) - ? { - publicationId: row.id, - version: cancellableConflicts.get(row.id)!, - } - : undefined, - ) - : row.state === "delivery_unknown" - ? (["mark_delivered", "retry_anyway", "cancel"] as const) - : [], - }; - }), - ...slashActions.map((row) => { - const recovery = slackSlashTaskRecoveryPayload(row.payload); - // Keep this GET read-only while still surfacing a crashed attempt as - // actionable. The resolution POST persists the derived quarantine - // before it applies the operator's audited decision. - const status = slackTaskStartActivityStatus(row); - return { - id: row.id, - kind: "action" as const, - actionType: "slash_task_start" as const, - status, - summary: `Slack slash-command task start ${status.replaceAll("_", " ")}`, - detail: - status === "delivery_unknown" - ? recovery - ? "Slack may have accepted the task-start message, so Paperclip will not replay it automatically. Check Slack first; an explicit retry can create a duplicate starter message and task." - : "Slack may have accepted the task-start message. This older action lacks the context required for a safe explicit retry, so check Slack and cancel it here before submitting a new command." - : status === "provider_confirmed" - ? "Slack accepted the task-start message. Paperclip is completing durable task admission without sending another Slack message." - : status === "admitting" - ? "Slack accepted the task-start message. A Paperclip worker is admitting the task without replaying the Slack send." - : status === "failed" - ? "Slack rejected the task-start message. Submit the command again to retry." - : status === "cancelled" - ? "An operator cancelled this unconfirmed task start." - : null, - createdAt: row.createdAt.toISOString(), - replayable: false, - resolutionActions: - status === "delivery_unknown" - ? recovery - ? (["retry_anyway", "cancel"] as const) - : (["cancel"] as const) - : [], - }; - }), - ...providerEffects.map((row) => { - const payload = providerEffectPayload(row.payload); - return { - id: row.id, - kind: "action" as const, - actionType: "provider_effect" as const, - status: row.status, - summary: "Provider reply delivery unknown", - detail: payload?.completeConversationId - ? "The provider may have accepted this reply, but Paperclip could not confirm it or close the task conversation. Check the provider first. Marking it delivered closes the conversation; retrying can create a duplicate message." - : "The provider may have accepted this reply, but Paperclip could not confirm it. Check the provider first. Retrying can create a duplicate message.", - createdAt: row.createdAt.toISOString(), - replayable: false, - resolutionActions: [ - "mark_delivered", - "retry_anyway", - "cancel", - ] as const, - }; - }), - ...slackSessionActions.map((row) => { - const isSync = row.kind === "slack_session_sync"; - const retrying = - row.status === "received" && - row.result?.code === "slack_session_sync_retry"; - return { - id: row.id, - kind: "action" as const, - actionType: isSync - ? ("slack_session_sync" as const) - : ("slack_session_stop" as const), - status: retrying ? "retry" : row.status, - summary: isSync ? "Slack session status" : "Slack Stop request", - detail: isSync - ? row.result?.outcome === "unavailable" - ? "Slack has not enabled native session status for this destination. Message delivery continues." - : row.result?.code === "slack_session_sync_rejected" - ? "Slack rejected the session indicator update. Check app permissions and channel access; message delivery is tracked separately." - : retrying - ? "The Slack session indicator is waiting to sync. Paperclip will not resend the response." - : row.result?.sessionStatus === "processing" - ? "Working status is refreshed automatically while the run remains active." - : null - : row.status === "processed" - ? "Paperclip stopped the work authorized by this request." - : row.status === "cancelled" - ? "No work was stopped: this request was no longer authorized or its target was no longer current." - : row.status === "failed" - ? row.result?.retryable === true - ? "The Stop request could not finish yet. Paperclip will retry against the original work only." - : "The Stop request could not be completed. Check the task's current run before trying again." - : "Paperclip is processing this Stop request against its original task and run.", - createdAt: row.updatedAt.toISOString(), - replayable: false, - resolutionActions: [], - }; - }), - ...githubIngressFailures.map((row) => { - const deliveryId = - typeof row.payload.deliveryId === "string" - ? row.payload.deliveryId - : null; - return { - id: row.id, - kind: "action" as const, - actionType: "github_webhook_ingress" as const, - status: row.status, - summary: "GitHub webhook could not be processed", - detail: deliveryId - ? `GitHub delivery ${deliveryId} was authenticated, but Paperclip could not finish processing it. Fix the connection or destination, then redeliver this delivery from the GitHub App's Recent Deliveries page.` - : "An authenticated GitHub webhook could not be processed. Fix the connection or destination, then redeliver it from the GitHub App's Recent Deliveries page.", - createdAt: row.createdAt.toISOString(), - replayable: false, - resolutionActions: [], - }; - }), - ...githubRecoveries.map(({ action: row, ingressStatus }) => ({ - id: row.id, - kind: "repair" as const, - status: - row.status === "cancelled" - ? "cancelled" - : ingressStatus - ? "received" - : "pending", - summary: - row.status === "cancelled" - ? "GitHub webhook recovery skipped" - : ingressStatus - ? "GitHub webhook received after recovery request" - : "GitHub webhook redelivery requested", - detail: - row.status === "cancelled" - ? row.result?.code === "source_changed_or_unavailable" - ? "The original comment changed or is no longer available. Paperclip did not replay its old contents." - : "This callback was not an eligible current user comment. Paperclip did not replay it." - : ingressStatus - ? "Paperclip received this callback. Its normal access checks and processing still apply." - : "Paperclip asked GitHub to resend a recent missed message. This does not yet confirm receipt or a reply. Automatic requests are limited; if it remains unanswered, check the App's Recent Deliveries and send your request again in the current conversation.", - createdAt: row.updatedAt.toISOString(), - replayable: false, - resolutionActions: [], - })), - ...githubRecoveryScans.flatMap((row) => { - const value = row.value as { outcome?: string } | null; - const detail = - value?.outcome === "history_limit_reached" - ? "Recent webhook volume exceeded automatic recovery's bounded scan. No partial history was replayed. Check the App's Recent Deliveries for missed messages." - : value?.outcome === "callback_mismatch" - ? "The GitHub App's callback no longer matches this connection. Reconnect the App to repair it; repository access will not change." - : value?.outcome === "scan_failed" - ? "Paperclip could not check GitHub's failed deliveries. The check will retry with backoff; normal callbacks are still processed." - : null; - return detail - ? [ - { - id: row.id, - kind: "health" as const, - status: "attention", - summary: "GitHub webhook recovery needs attention", - detail, - createdAt: row.updatedAt.toISOString(), - replayable: false, - resolutionActions: [], - }, - ] - : []; - }), - ] - .sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id)) - .slice(0, limit); - } - - async function listActivityPage(endpointId: string, limit = 25, cursor?: string) { - if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw badRequest("Activity limit must be between 1 and 100"); - let before: { createdAt: string; id: string } | undefined; - if (cursor !== undefined) { - try { - if (cursor.length > 256) throw new Error("Invalid cursor"); - const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")); - if (!Array.isArray(value) || value.length !== 2 - || typeof value[0] !== "string" || new Date(value[0]).toISOString() !== value[0] - || typeof value[1] !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value[1])) throw new Error("Invalid cursor"); - before = { createdAt: value[0], id: value[1] }; - } catch { throw badRequest("Invalid activity cursor"); } - } - const rows = await listActivity(endpointId, { limit, before }); - const items = rows.slice(0, limit); - const last = items.at(-1); - return { - items, - nextCursor: rows.length > limit && last - ? Buffer.from(JSON.stringify([last.createdAt, last.id])).toString("base64url") - : null, - }; - } - async function replayDelivery(endpointId: string, deliveryId: string) { const delivery = await db .select() @@ -22685,118 +17763,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }); } - async function publicationFiles( - publication: typeof chatPublications.$inferSelect, - payload: SafeChatPublicationPayload, - preparation: { teamsInlineImagesOnly?: boolean } = {}, - ): Promise { - if (!payload.attachmentIds?.length) return []; - try { - const rows = await db - .select({ - id: issueAttachments.id, - issueId: issueAttachments.issueId, - issueCommentId: issueAttachments.issueCommentId, - objectKey: assets.objectKey, - contentType: assets.contentType, - byteSize: assets.byteSize, - sha256: assets.sha256, - originalFilename: assets.originalFilename, - }) - .from(issueAttachments) - .innerJoin(assets, eq(issueAttachments.assetId, assets.id)) - .where( - and( - eq(issueAttachments.companyId, publication.companyId), - eq(issueAttachments.issueId, publication.issueId), - inArray(issueAttachments.id, payload.attachmentIds), - ), - ); - const byId = new Map(rows.map((row) => [row.id, row])); - const orderedRows = payload.attachmentIds.map((attachmentId) => { - const row = byId.get(attachmentId); - if ( - !row || - (publication.commentId && - row.issueCommentId !== publication.commentId) || - row.byteSize <= 0 || - row.byteSize > MAX_ATTACHMENT_BYTES || - !row.objectKey.trim() || - !/^[0-9a-f]{64}$/iu.test(row.sha256) || - !isAllowedContentType(row.contentType) - ) - throw new OutboundAttachmentValidationError( - "Chat publication attachment is invalid or outside its authorized task comment", - ); - return row; - }); - // Picture messages have a different Teams contract from arbitrary files. - // Preserve the normal scope/metadata checks above, but never hydrate a - // document or oversized image just to discover it needs a task link. - if ( - preparation.teamsInlineImagesOnly && - orderedRows.some( - (row) => - row.byteSize > TEAMS_INLINE_IMAGE_MAX_BYTES || - !isTeamsInlineImageContentType(row.contentType), - ) - ) { - return []; - } - // Authorization and persisted metadata must fail definitively even when - // storage is unavailable; do not mask an invalid file as a transient outage. - if (!options.storage) - throw new OutboundAttachmentHydrationError( - "Attachment storage is unavailable for chat publication", - ); - const files: FileUpload[] = []; - for (const row of orderedRows) { - files.push( - await hydrateOutboundAttachment({ - storage: options.storage, - companyId: publication.companyId, - objectKey: row.objectKey, - byteSize: row.byteSize, - sha256: row.sha256, - filename: row.originalFilename ?? `attachment-${row.id}`, - mimeType: row.contentType, - }), - ); - } - return files; - } catch (error) { - if ( - isOutboundAttachmentHydrationError(error) || - isOutboundAttachmentValidationError(error) - ) { - throw error; - } - throw new OutboundAttachmentHydrationError( - "Chat publication attachment preparation failed", - error, - ); - } - } - - function discordMarkdownAttachment(text: string): FileUpload { - return { - data: Buffer.from(text, "utf8"), - filename: "paperclip-response.md", - mimeType: "text/markdown; charset=utf-8", - }; - } - - function telegramMarkdownAttachment(text: string): Attachment { - const data = Buffer.from(text, "utf8"); - return { - data, - mimeType: "text/markdown; charset=utf-8", - name: "paperclip-response.md", - size: data.byteLength, - type: "file", - }; - } - async function recordExactOutboundPublicationLink( tx: DbOrTransaction, input: { @@ -26176,6 +21142,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }, lease, actor.userId); }, }); + const { verifySlackRequestUrl } = createSlackCallbackVerification(db, { resolveCredentialRefs, getWebhookPublicBaseUrl, scheduleMessageProcessing, slackRegistration }); const sendSlackTestMessage = slackTestMessageService(db, { fetch: fetchImpl, diff --git a/server/src/services/chat-channels/activity.ts b/server/src/services/chat-channels/activity.ts new file mode 100644 index 00000000000..0a9824ae577 --- /dev/null +++ b/server/src/services/chat-channels/activity.ts @@ -0,0 +1,616 @@ +import { providerEffectPayload } from "./provider-effects.js"; + +import { slackSlashTaskRecoveryPayload } from "./slack/task-start.js"; +import { GITHUB_RECOVERY_STATE_KEY } from "./github/webhook-ingress.js"; + +import { + and, + desc, + eq, + inArray, + sql, +} from "drizzle-orm"; +import { alias, type AnyPgColumn } from "drizzle-orm/pg-core"; + +import type { Db } from "@paperclipai/db"; + +import { + chatActions, + chatConversations, + chatDeliveries, + chatPublications, + chatSdkState, + chatTeamsFileTransfers, + issues, +} from "@paperclipai/db"; +import type { + ChatPublicationSummary, + SafeChatPublicationPayload, +} from "@paperclipai/shared"; + +import { + teamsFileTransferService, +} from "../chat-teams-file-transfers.js"; + +import { + chatFileTransferResolutionActions, + projectChatFileTransfer, +} from "../chat-publication-batches.js"; + +import { + badRequest, + notFound, +} from "../../errors.js"; + +import { chatProviderConversationUrl } from "../chat-provider-links.js"; + +import type { EndpointRecord } from "./runtime-types.js"; +import { slackTaskStartActivityStatus } from "./slack/task-start.js"; +import { fileTransferProjectionColumns } from "./microsoft-teams/file-transfer-projection.js"; + +function publicationSummary( + row: typeof chatPublications.$inferSelect, +): ChatPublicationSummary { + return { + id: row.id, + state: row.state, + providerUrl: row.providerUrl, + attempts: row.attempts, + redactedError: row.redactedError, + nextAttemptAt: row.nextAttemptAt?.toISOString() ?? null, + publishedAt: row.publishedAt?.toISOString() ?? null, + }; +} +export { publicationSummary }; + +type ChatActivityOptions = { + endpointRecord: (endpointId: string) => Promise; + teamsTransferProtocol: () => Pick, "canCancelConflict">; +}; + +export function createChatActivity(db: Db, options: ChatActivityOptions) { + const { endpointRecord, teamsTransferProtocol } = options; + + + async function listConversations(endpointId: string) { + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + const conversations = await db + .select() + .from(chatConversations) + .where(eq(chatConversations.endpointId, endpointId)) + .orderBy( + desc(chatConversations.lastActivityAt), + desc(chatConversations.createdAt), + ); + if (!conversations.length) return []; + const issueRows = await db + .select({ + id: issues.id, + identifier: issues.identifier, + title: issues.title, + status: issues.status, + updatedAt: issues.updatedAt, + }) + .from(issues) + .where( + inArray( + issues.id, + conversations.map((row) => row.issueId), + ), + ); + const publications = await db + .select() + .from(chatPublications) + .where(eq(chatPublications.endpointId, endpointId)) + .orderBy(desc(chatPublications.createdAt)); + const issueById = new Map(issueRows.map((row) => [row.id, row])); + const lastPublication = new Map< + string, + typeof chatPublications.$inferSelect + >(); + for (const publication of publications) + if (!lastPublication.has(publication.conversationId)) + lastPublication.set(publication.conversationId, publication); + return conversations.map((conversation) => { + const issue = issueById.get(conversation.issueId); + const externalUrl = + record.endpoint.provider === "telegram" && conversation.isDirectMessage + ? chatProviderConversationUrl({ + provider: "telegram", + botUsername: record.endpoint.botUsername, + threadId: conversation.externalThreadId, + providerMessageId: "", + raw: { chat: { type: "private" } }, + }) + : (conversation.providerUrl ?? + (record.endpoint.provider === "slack" + ? chatProviderConversationUrl({ + provider: "slack", + providerAccountId: record.endpoint.providerAccountId, + threadId: conversation.externalThreadId, + providerMessageId: "", + }) + : null)); + return { + id: conversation.id, + companyId: conversation.companyId, + endpointId: conversation.endpointId, + resourceId: conversation.resourceId, + externalLabel: conversation.externalLabel, + externalUrl, + externalConversationId: conversation.externalConversationId, + externalThreadId: conversation.externalThreadId, + sessionGeneration: conversation.sessionGeneration, + issueId: conversation.issueId, + issueIdentifier: issue?.identifier ?? null, + issueTitle: issue?.title ?? null, + isDirectMessage: conversation.isDirectMessage, + state: + record.endpoint.provider !== "imessage-photon" && + (issue?.status === "done" || issue?.status === "cancelled") + ? "completed" + : conversation.state, + lastActivityAt: conversation.lastActivityAt?.toISOString() ?? null, + createdAt: conversation.createdAt.toISOString(), + updatedAt: ( + conversation.lastActivityAt ?? conversation.updatedAt + ).toISOString(), + lastPublicationStatus: + lastPublication.get(conversation.id)?.state ?? null, + }; + }); + } + + async function listActivity(endpointId: string, page?: { limit: number; before?: { createdAt: string; id: string } }) { + const limit = page ? page.limit + 1 : 100; + // Match the millisecond precision of the public timestamp and use a UUID + // tie-breaker so equal timestamps never skip records between pages. + const before = (date: AnyPgColumn, id: AnyPgColumn) => page?.before + ? sql`(date_trunc('milliseconds', ${date}), ${id}) < (${page.before.createdAt}::timestamptz, ${page.before.id}::uuid)` + : undefined; + const recoveryIngress = alias(chatActions, "github_recovery_ingress"); + const [ + deliveries, + publications, + slashActions, + providerEffects, + githubIngressFailures, + slackSessionActions, + githubRecoveries, + githubRecoveryScans, + ] = await Promise.all([ + db + .select() + .from(chatDeliveries) + .where(and(eq(chatDeliveries.endpointId, endpointId), before(chatDeliveries.createdAt, chatDeliveries.id))) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatDeliveries.createdAt})`), desc(chatDeliveries.id)) + .limit(limit), + db + .select() + .from(chatPublications) + .where(and(eq(chatPublications.endpointId, endpointId), before(chatPublications.createdAt, chatPublications.id))) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatPublications.createdAt})`), desc(chatPublications.id)) + .limit(limit), + db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, endpointId), + before(chatActions.createdAt, chatActions.id), + eq(chatActions.kind, "slash_task_start"), + ), + ) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) + .limit(limit), + db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, endpointId), + before(chatActions.createdAt, chatActions.id), + eq(chatActions.kind, "provider_effect"), + eq(chatActions.status, "delivery_unknown"), + ), + ) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) + .limit(limit), + db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, endpointId), + before(chatActions.createdAt, chatActions.id), + eq(chatActions.kind, "github_webhook_ingress"), + eq(chatActions.status, "failed"), + sql`coalesce(${chatActions.result}->>'retryable', 'false') = 'false'`, + ), + ) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.createdAt})`), desc(chatActions.id)) + .limit(limit), + db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, endpointId), + before(chatActions.updatedAt, chatActions.id), + inArray(chatActions.kind, [ + "slack_session_sync", + "slack_session_stop", + ]), + ), + ) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.updatedAt})`), desc(chatActions.id)) + .limit(limit), + db + .select({ + action: chatActions, + ingressStatus: recoveryIngress.status, + }) + .from(chatActions) + .leftJoin( + recoveryIngress, + and( + eq(recoveryIngress.companyId, chatActions.companyId), + eq(recoveryIngress.endpointId, chatActions.endpointId), + eq(recoveryIngress.kind, "github_webhook_ingress"), + sql`${recoveryIngress.providerActionId} = 'github_webhook_ingress:' || (${chatActions.payload}#>>'{original,guid}')`, + ), + ) + .where( + and( + eq(chatActions.endpointId, endpointId), + before(chatActions.updatedAt, chatActions.id), + eq(chatActions.kind, "github_webhook_recovery"), + ), + ) + .orderBy(desc(sql`date_trunc('milliseconds', ${chatActions.updatedAt})`), desc(chatActions.id)) + .limit(limit), + db + .select() + .from(chatSdkState) + .where( + and( + eq(chatSdkState.endpointId, endpointId), + before(chatSdkState.updatedAt, chatSdkState.id), + eq(chatSdkState.stateKey, GITHUB_RECOVERY_STATE_KEY), + ), + ) + .limit(1), + ]); + // A provider effect can be on another page. Replay safety must still + // consider every unresolved effect associated with these deliveries. + const ambiguousEffects = deliveries.length ? await db.select({ deliveryId: chatActions.deliveryId }) + .from(chatActions).where(and( + eq(chatActions.endpointId, endpointId), + eq(chatActions.kind, "provider_effect"), + eq(chatActions.status, "delivery_unknown"), + inArray(chatActions.deliveryId, deliveries.map((row) => row.id)), + )) : []; + const ambiguousProviderEffectDeliveryIds = new Set(ambiguousEffects.map((row) => row.deliveryId)); + const transferRows = publications.length + ? await db + .select(fileTransferProjectionColumns) + .from(chatTeamsFileTransfers) + .innerJoin( + chatPublications, + and( + eq(chatPublications.id, chatTeamsFileTransfers.publicationId), + eq(chatPublications.companyId, chatTeamsFileTransfers.companyId), + eq( + chatPublications.endpointId, + chatTeamsFileTransfers.endpointId, + ), + eq( + chatPublications.conversationId, + chatTeamsFileTransfers.conversationId, + ), + ), + ) + .where( + and( + eq(chatTeamsFileTransfers.companyId, publications[0]!.companyId), + eq(chatTeamsFileTransfers.endpointId, endpointId), + inArray( + chatTeamsFileTransfers.publicationId, + publications.map((row) => row.id), + ), + ), + ) + : []; + const transfers = new Map( + transferRows.map((row) => [row.publicationId, row]), + ); + const cancellableConflicts = new Map(); + for (const transfer of transferRows) { + if (transfer.phase !== "conflict") continue; + const publication = publications.find( + (row) => row.id === transfer.publicationId, + ); + if (!publication?.conversationId) continue; + if ( + await teamsTransferProtocol().canCancelConflict({ + companyId: publication.companyId, + endpointId, + conversationId: publication.conversationId, + publicationId: publication.id, + version: transfer.version, + }) + ) + cancellableConflicts.set(publication.id, transfer.version); + } + return [ + ...deliveries.map((row) => { + const normalized = + row.normalizedEvent && + typeof row.normalizedEvent === "object" && + !Array.isArray(row.normalizedEvent) + ? (row.normalizedEvent as Record) + : {}; + const deduplication = + normalized.deduplication && + typeof normalized.deduplication === "object" && + !Array.isArray(normalized.deduplication) + ? (normalized.deduplication as Record) + : null; + const duplicateCount = + typeof deduplication?.duplicateCount === "number" + ? deduplication.duplicateCount + : 0; + const label = row.eventKind.replaceAll("_", " "); + const outcome = + row.state === "filtered" + ? "ignored" + : row.state === "processed" + ? "processed" + : row.state; + return { + id: row.id, + kind: "delivery" as const, + status: row.state, + summary: `${label} ${outcome}${duplicateCount ? ` · ${duplicateCount} duplicate${duplicateCount === 1 ? "" : "s"} ignored` : ""}`, + detail: row.redactedError, + createdAt: row.createdAt.toISOString(), + replayable: + row.state === "failed" && + Boolean(row.conversationId) && + !ambiguousProviderEffectDeliveryIds.has(row.id), + resolutionActions: [], + }; + }), + ...publications.map((row) => { + const transfer = transfers.get(row.id); + const projected = projectChatFileTransfer( + publicationSummary(row), + transfer, + ); + const payload = row.payload as Partial; + const summary = payload.progressState + ? `${payload.progressState.replaceAll("_", " ")} update` + : payload.interactionId + ? "Interactive question" + : "Response to external conversation"; + return { + id: row.id, + kind: "publication" as const, + status: projected.state, + summary, + detail: projected.redactedError ?? null, + createdAt: row.createdAt.toISOString(), + replayable: !transfer && row.state === "failed", + ...(projected.fileTransfer + ? { fileTransfer: projected.fileTransfer } + : {}), + resolutionActions: transfer + ? chatFileTransferResolutionActions( + projected, + cancellableConflicts.has(row.id) + ? { + publicationId: row.id, + version: cancellableConflicts.get(row.id)!, + } + : undefined, + ) + : row.state === "delivery_unknown" + ? (["mark_delivered", "retry_anyway", "cancel"] as const) + : [], + }; + }), + ...slashActions.map((row) => { + const recovery = slackSlashTaskRecoveryPayload(row.payload); + // Keep this GET read-only while still surfacing a crashed attempt as + // actionable. The resolution POST persists the derived quarantine + // before it applies the operator's audited decision. + const status = slackTaskStartActivityStatus(row); + return { + id: row.id, + kind: "action" as const, + actionType: "slash_task_start" as const, + status, + summary: `Slack slash-command task start ${status.replaceAll("_", " ")}`, + detail: + status === "delivery_unknown" + ? recovery + ? "Slack may have accepted the task-start message, so Paperclip will not replay it automatically. Check Slack first; an explicit retry can create a duplicate starter message and task." + : "Slack may have accepted the task-start message. This older action lacks the context required for a safe explicit retry, so check Slack and cancel it here before submitting a new command." + : status === "provider_confirmed" + ? "Slack accepted the task-start message. Paperclip is completing durable task admission without sending another Slack message." + : status === "admitting" + ? "Slack accepted the task-start message. A Paperclip worker is admitting the task without replaying the Slack send." + : status === "failed" + ? "Slack rejected the task-start message. Submit the command again to retry." + : status === "cancelled" + ? "An operator cancelled this unconfirmed task start." + : null, + createdAt: row.createdAt.toISOString(), + replayable: false, + resolutionActions: + status === "delivery_unknown" + ? recovery + ? (["retry_anyway", "cancel"] as const) + : (["cancel"] as const) + : [], + }; + }), + ...providerEffects.map((row) => { + const payload = providerEffectPayload(row.payload); + return { + id: row.id, + kind: "action" as const, + actionType: "provider_effect" as const, + status: row.status, + summary: "Provider reply delivery unknown", + detail: payload?.completeConversationId + ? "The provider may have accepted this reply, but Paperclip could not confirm it or close the task conversation. Check the provider first. Marking it delivered closes the conversation; retrying can create a duplicate message." + : "The provider may have accepted this reply, but Paperclip could not confirm it. Check the provider first. Retrying can create a duplicate message.", + createdAt: row.createdAt.toISOString(), + replayable: false, + resolutionActions: [ + "mark_delivered", + "retry_anyway", + "cancel", + ] as const, + }; + }), + ...slackSessionActions.map((row) => { + const isSync = row.kind === "slack_session_sync"; + const retrying = + row.status === "received" && + row.result?.code === "slack_session_sync_retry"; + return { + id: row.id, + kind: "action" as const, + actionType: isSync + ? ("slack_session_sync" as const) + : ("slack_session_stop" as const), + status: retrying ? "retry" : row.status, + summary: isSync ? "Slack session status" : "Slack Stop request", + detail: isSync + ? row.result?.outcome === "unavailable" + ? "Slack has not enabled native session status for this destination. Message delivery continues." + : row.result?.code === "slack_session_sync_rejected" + ? "Slack rejected the session indicator update. Check app permissions and channel access; message delivery is tracked separately." + : retrying + ? "The Slack session indicator is waiting to sync. Paperclip will not resend the response." + : row.result?.sessionStatus === "processing" + ? "Working status is refreshed automatically while the run remains active." + : null + : row.status === "processed" + ? "Paperclip stopped the work authorized by this request." + : row.status === "cancelled" + ? "No work was stopped: this request was no longer authorized or its target was no longer current." + : row.status === "failed" + ? row.result?.retryable === true + ? "The Stop request could not finish yet. Paperclip will retry against the original work only." + : "The Stop request could not be completed. Check the task's current run before trying again." + : "Paperclip is processing this Stop request against its original task and run.", + createdAt: row.updatedAt.toISOString(), + replayable: false, + resolutionActions: [], + }; + }), + ...githubIngressFailures.map((row) => { + const deliveryId = + typeof row.payload.deliveryId === "string" + ? row.payload.deliveryId + : null; + return { + id: row.id, + kind: "action" as const, + actionType: "github_webhook_ingress" as const, + status: row.status, + summary: "GitHub webhook could not be processed", + detail: deliveryId + ? `GitHub delivery ${deliveryId} was authenticated, but Paperclip could not finish processing it. Fix the connection or destination, then redeliver this delivery from the GitHub App's Recent Deliveries page.` + : "An authenticated GitHub webhook could not be processed. Fix the connection or destination, then redeliver it from the GitHub App's Recent Deliveries page.", + createdAt: row.createdAt.toISOString(), + replayable: false, + resolutionActions: [], + }; + }), + ...githubRecoveries.map(({ action: row, ingressStatus }) => ({ + id: row.id, + kind: "repair" as const, + status: + row.status === "cancelled" + ? "cancelled" + : ingressStatus + ? "received" + : "pending", + summary: + row.status === "cancelled" + ? "GitHub webhook recovery skipped" + : ingressStatus + ? "GitHub webhook received after recovery request" + : "GitHub webhook redelivery requested", + detail: + row.status === "cancelled" + ? row.result?.code === "source_changed_or_unavailable" + ? "The original comment changed or is no longer available. Paperclip did not replay its old contents." + : "This callback was not an eligible current user comment. Paperclip did not replay it." + : ingressStatus + ? "Paperclip received this callback. Its normal access checks and processing still apply." + : "Paperclip asked GitHub to resend a recent missed message. This does not yet confirm receipt or a reply. Automatic requests are limited; if it remains unanswered, check the App's Recent Deliveries and send your request again in the current conversation.", + createdAt: row.updatedAt.toISOString(), + replayable: false, + resolutionActions: [], + })), + ...githubRecoveryScans.flatMap((row) => { + const value = row.value as { outcome?: string } | null; + const detail = + value?.outcome === "history_limit_reached" + ? "Recent webhook volume exceeded automatic recovery's bounded scan. No partial history was replayed. Check the App's Recent Deliveries for missed messages." + : value?.outcome === "callback_mismatch" + ? "The GitHub App's callback no longer matches this connection. Reconnect the App to repair it; repository access will not change." + : value?.outcome === "scan_failed" + ? "Paperclip could not check GitHub's failed deliveries. The check will retry with backoff; normal callbacks are still processed." + : null; + return detail + ? [ + { + id: row.id, + kind: "health" as const, + status: "attention", + summary: "GitHub webhook recovery needs attention", + detail, + createdAt: row.updatedAt.toISOString(), + replayable: false, + resolutionActions: [], + }, + ] + : []; + }), + ] + .sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id)) + .slice(0, limit); + } + + async function listActivityPage(endpointId: string, limit = 25, cursor?: string) { + if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw badRequest("Activity limit must be between 1 and 100"); + let before: { createdAt: string; id: string } | undefined; + if (cursor !== undefined) { + try { + if (cursor.length > 256) throw new Error("Invalid cursor"); + const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")); + if (!Array.isArray(value) || value.length !== 2 + || typeof value[0] !== "string" || new Date(value[0]).toISOString() !== value[0] + || typeof value[1] !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value[1])) throw new Error("Invalid cursor"); + before = { createdAt: value[0], id: value[1] }; + } catch { throw badRequest("Invalid activity cursor"); } + } + const rows = await listActivity(endpointId, { limit, before }); + const items = rows.slice(0, limit); + const last = items.at(-1); + return { + items, + nextCursor: rows.length > limit && last + ? Buffer.from(JSON.stringify([last.createdAt, last.id])).toString("base64url") + : null, + }; + } + return { listConversations, listActivity, listActivityPage }; +} diff --git a/server/src/services/chat-channels/discord/gateway-events.ts b/server/src/services/chat-channels/discord/gateway-events.ts new file mode 100644 index 00000000000..e283fdc8630 --- /dev/null +++ b/server/src/services/chat-channels/discord/gateway-events.ts @@ -0,0 +1,253 @@ +import type { EndpointRow, DbTransaction, RuntimeContext, LifecycleRuntimeFence, InternalSetupState } from "../runtime-types.js"; + +import { notifyChatEndpointWork } from "../../chat-work-notifications.js"; + +import { + and, + eq, + inArray, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + chatConversations, + chatEndpointResources, + chatEndpoints, + toolConnections, +} from "@paperclipai/db"; +import type { + ChatEndpointSetupState, +} from "@paperclipai/shared"; + +import { + type DiscordGatewayCallbackEvent, +} from "../../chat-sdk-runtime.js"; + +import { + type ChatProviderLifecycleEffect, +} from "../../chat-provider-lifecycle.js"; + +import type { EndpointRecord } from "../runtime-types.js"; + +type DiscordGatewayEventsOptions = { + ensureDiscordGatewayRuntimeIsCurrent: (endpointId: string, context: RuntimeContext) => Promise; + discordGatewayRuntimeIsCurrent: (endpointId: string, context: RuntimeContext) => boolean; + runtimeCallbackRecord: (endpointId: string, context: RuntimeContext, statuses: EndpointRow["status"][]) => Promise; + runtimeCallbackEndpoint: (tx: DbTransaction, endpointId: string, context: LifecycleRuntimeFence, statuses: EndpointRow["status"][]) => Promise; + applyProviderLifecycleEffect: (endpoint: EndpointRow, effect: ChatProviderLifecycleEffect, context?: RuntimeContext) => Promise; + runtimeGeneration: (setup: ChatEndpointSetupState) => number; + discordGatewayEventBarrier?: (event: DiscordGatewayCallbackEvent) => Promise; +}; + +export function createDiscordGatewayEvents(db: Db, options: DiscordGatewayEventsOptions) { + const { ensureDiscordGatewayRuntimeIsCurrent, discordGatewayRuntimeIsCurrent, runtimeCallbackRecord, runtimeCallbackEndpoint, applyProviderLifecycleEffect, runtimeGeneration } = options; + + + function discordGatewayFailureDetail( + event: DiscordGatewayCallbackEvent["event"], + ): string { + if (event.type === "guild_removed") { + return "Discord bot was removed from the configured server"; + } + if (event.type === "guild_unavailable") { + return "Discord server is temporarily unavailable; reconnecting"; + } + if (event.type === "disconnected") { + return `Discord Gateway disconnected${event.code === undefined ? "" : ` (code ${event.code})`}; reconnecting`; + } + if (event.type === "failure") { + const code = event.error.code ?? event.error.status; + return `Discord Gateway failed${code === undefined ? "" : ` (${String(code).slice(0, 64)})`}; ${event.fatal ? "credentials or privileged intents need attention" : "reconnecting"}`; + } + return "Discord Gateway connection needs attention"; + } + + function handleDiscordGatewayEvent( + callback: DiscordGatewayCallbackEvent, + context: RuntimeContext, + ): Promise { + const pending = (context.discordGatewayEventQueue ?? Promise.resolve()) + .catch(() => undefined) + .then(() => processDiscordGatewayEvent(callback, context)); + context.discordGatewayEventQueue = pending; + return pending; + } + + async function processDiscordGatewayEvent( + callback: DiscordGatewayCallbackEvent, + context: RuntimeContext, + ): Promise { + if ( + !(await ensureDiscordGatewayRuntimeIsCurrent( + callback.endpointId, + context, + )) + ) + return; + if (callback.sequence <= (context.discordGatewaySequence ?? 0)) return; + context.discordGatewaySequence = callback.sequence; + await options.discordGatewayEventBarrier?.(callback); + const record = await runtimeCallbackRecord(callback.endpointId, context, [ + "verifying", + "active", + "attention", + ]); + if (!record || record.endpoint.provider !== "discord") return; + const event = callback.event; + if (event.type === "channel_removed") { + await applyProviderLifecycleEffect( + record.endpoint, + { + kind: "resource", + provider: "discord", + providerEventId: `gateway:channel_removed:${event.channelId}`, + providerResourceId: event.channelId, + resourceType: "channel", + label: event.label?.trim() || event.channelId, + availability: "removed", + metadata: { source: "discord_gateway" }, + }, + context, + ); + return; + } + + await db.transaction(async (tx) => { + const current = await runtimeCallbackEndpoint( + tx, + callback.endpointId, + context, + ["verifying", "active", "attention"], + ); + if ( + !current || + current.provider !== "discord" || + !discordGatewayRuntimeIsCurrent(callback.endpointId, context) + ) { + return; + } + const now = new Date(); + if (event.type === "connecting") { + await tx + .update(chatEndpoints) + .set({ + healthMessage: "Discord Gateway connecting", + lastError: null, + lastEventAt: now, + updatedAt: now, + }) + .where(eq(chatEndpoints.id, current.id)); + await tx + .update(toolConnections) + .set({ + healthStatus: "unchecked", + healthMessage: "Discord Gateway connecting", + lastError: null, + healthCheckedAt: now, + updatedAt: now, + }) + .where(eq(toolConnections.id, current.connectionId)); + return; + } + if (event.type === "ready" || event.type === "guild_available") { + const gatewayOwnedAttention = + current.status === "attention" && + current.healthMessage?.startsWith("Discord ") === true; + if (current.status === "attention" && !gatewayOwnedAttention) return; + const status = + current.setup.step === "complete" ? "active" : "verifying"; + if (status !== current.status) await notifyChatEndpointWork(tx); + await tx + .update(chatEndpoints) + .set({ + status, + healthMessage: + status === "active" + ? "Connected" + : "Waiting for a test conversation", + lastError: null, + lastEventAt: now, + updatedAt: now, + }) + .where(eq(chatEndpoints.id, current.id)); + await tx + .update(toolConnections) + .set({ + status: "active", + enabled: true, + healthStatus: "healthy", + healthMessage: "Connected", + lastError: null, + healthCheckedAt: now, + updatedAt: now, + }) + .where(eq(toolConnections.id, current.connectionId)); + return; + } + + const detail = discordGatewayFailureDetail(event); + const transientGuildOutage = event.type === "guild_unavailable"; + const terminal = + event.type === "guild_removed" || + ((event.type === "failure" || event.type === "disconnected") && + event.fatal); + await tx + .update(chatEndpoints) + .set({ + status: transientGuildOutage + ? current.status + : event.type === "guild_removed" + ? "revoked" + : "attention", + healthMessage: detail, + lastError: detail, + lastEventAt: now, + ...(terminal + ? { + setup: { + ...current.setup, + runtimeGeneration: runtimeGeneration(current.setup) + 1, + } as InternalSetupState, + } + : {}), + updatedAt: now, + }) + .where(eq(chatEndpoints.id, current.id)); + await tx + .update(toolConnections) + .set({ + status: terminal ? "disabled" : "active", + enabled: !terminal, + healthStatus: event.type === "guild_removed" ? "failed" : "degraded", + healthMessage: detail, + lastError: detail, + healthCheckedAt: now, + updatedAt: now, + }) + .where(eq(toolConnections.id, current.connectionId)); + if (event.type === "guild_removed") { + await tx + .update(chatEndpointResources) + .set({ availability: "unavailable", updatedAt: now }) + .where( + and( + eq(chatEndpointResources.companyId, current.companyId), + eq(chatEndpointResources.endpointId, current.id), + ), + ); + await tx + .update(chatConversations) + .set({ state: "unavailable", updatedAt: now }) + .where( + and( + eq(chatConversations.companyId, current.companyId), + eq(chatConversations.endpointId, current.id), + inArray(chatConversations.state, ["active", "waiting"]), + ), + ); + } + }); + } + return { handleDiscordGatewayEvent }; +} diff --git a/server/src/services/chat-channels/discord/messages.ts b/server/src/services/chat-channels/discord/messages.ts index f61b2a2e8ef..61582abed26 100644 --- a/server/src/services/chat-channels/discord/messages.ts +++ b/server/src/services/chat-channels/discord/messages.ts @@ -1,3 +1,4 @@ +import type { FileUpload } from "chat"; import { createHash } from "node:crypto"; @@ -28,3 +29,11 @@ export function discordLifecycleFilesDigest(raw: unknown): string { : []; return createHash("sha256").update(JSON.stringify(projection)).digest("hex"); } + +export function discordMarkdownAttachment(text: string): FileUpload { + return { + data: Buffer.from(text, "utf8"), + filename: "paperclip-response.md", + mimeType: "text/markdown; charset=utf-8", + }; + } diff --git a/server/src/services/chat-channels/discord/root-threads.ts b/server/src/services/chat-channels/discord/root-threads.ts new file mode 100644 index 00000000000..1772ce375bb --- /dev/null +++ b/server/src/services/chat-channels/discord/root-threads.ts @@ -0,0 +1,570 @@ +import type { EndpointRow, DbTransaction, DbOrTransaction, RuntimeContext, LifecycleRuntimeFence, InboundRuntimeContext } from "../runtime-types.js"; + +import { notifyChatDeliveryWork } from "../../chat-work-notifications.js"; + +import type { ChatEndpointRuntime } from "../../voice/voice-runtime.js"; + +import { chatCredentialMutationLease } from "../../chat-credential-mutation-lease.js"; + +import { + createHash, +} from "node:crypto"; +import { + and, + eq, + inArray, + sql, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + chatConversations, + chatDeliveries, + chatEndpointResources, + chatEndpoints, + chatExternalPrincipals, + companyMemberships, +} from "@paperclipai/db"; + +import { + conflict, +} from "../../../errors.js"; +import { logger } from "../../../middleware/logger.js"; + +import { + DiscordAdapterCompatibilityError, + type ChatSdkMessageCallbackEvent, + type DiscordRootMentionAdmissionEvent, +} from "../../chat-sdk-runtime.js"; + +import { classifyChatPublicationError } from "../../chat-publication-errors.js"; + +import type { + Message, + Thread, +} from "chat"; + +type DeliveryRow = typeof chatDeliveries.$inferSelect; + +type DiscordRootThreadsOptions = { + ensureDiscordGatewayRuntimeIsCurrent: (endpointId: string, context: RuntimeContext) => Promise; + withCredentialMutationLease: ReturnType; + runtimeCallbackEndpoint: (tx: DbTransaction, endpointId: string, context: LifecycleRuntimeFence, statuses: EndpointRow["status"][]) => Promise; + lockCurrentPrincipalAuthorization: (tx: DbOrTransaction, endpoint: EndpointRow, principalId: string) => Promise<{ allowed: boolean; linkedDenied: boolean }>; + processMessage: (endpoint: EndpointRow, thread: Thread, message: Message, trigger: ChatSdkMessageCallbackEvent["trigger"], ingressOnly?: boolean, recoveredProviderUrl?: string | null, runtimeContext?: InboundRuntimeContext, providerUpdateId?: number, admittedDeliveryId?: string | null, receiptReactionSupported?: boolean, preauthorizedUserId?: string | null, deferDrainUntilFollowup?: boolean) => Promise; + redactError: (error: unknown) => string; + scheduleConversationDrain: (endpointId: string, threadId: string) => void; + lifecycleRuntimeFence: (delivery: DeliveryRow) => LifecycleRuntimeFence | null; + runtimeFor: (endpoint: EndpointRow) => Promise; + DISCORD_ROOT_THREAD_CONFIRMATION_DELAY_MS: number; + discordRootThreadTransportBarrier?: () => Promise; +}; + +export function createDiscordRootThreads(db: Db, options: DiscordRootThreadsOptions) { + const { ensureDiscordGatewayRuntimeIsCurrent, withCredentialMutationLease, runtimeCallbackEndpoint, lockCurrentPrincipalAuthorization, processMessage, redactError, scheduleConversationDrain, lifecycleRuntimeFence, runtimeFor, DISCORD_ROOT_THREAD_CONFIRMATION_DELAY_MS } = options; + + + async function admitDiscordRootMention( + configuredEndpoint: EndpointRow, + event: DiscordRootMentionAdmissionEvent, + context: RuntimeContext, + ): Promise { + if ( + !(await ensureDiscordGatewayRuntimeIsCurrent( + configuredEndpoint.id, + context, + )) + ) { + return false; + } + return withCredentialMutationLease( + configuredEndpoint, + async (credentialLease) => { + const threadId = `discord:${event.guildId}:${event.channelId}:${event.messageId}`; + const providerEventId = `${threadId}:${event.messageId}`; + const allowed = await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + const endpoint = await runtimeCallbackEndpoint( + tx, + configuredEndpoint.id, + context, + ["verifying", "active"], + ); + const existing = await tx + .select({ state: chatDeliveries.state }) + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.endpointId, configuredEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + ), + ) + .then((rows) => rows[0] ?? null); + if ( + existing && + ["processed", "filtered", "failed"].includes(existing.state) + ) + return false; + + let reason: string | null = null; + if (!endpoint || endpoint.providerAccountId !== event.guildId) { + reason = "Connection is not active"; + } + const resource = endpoint + ? await tx + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.endpointId, endpoint.id), + eq(chatEndpointResources.type, "channel"), + eq( + chatEndpointResources.providerResourceId, + event.channelId, + ), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null) + : null; + const enabledCount = + endpoint?.status === "verifying" + ? await tx + .select({ count: sql`count(*)::int` }) + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.endpointId, endpoint.id), + eq(chatEndpointResources.type, "channel"), + eq(chatEndpointResources.enabled, true), + eq(chatEndpointResources.availability, "available"), + ), + ) + .then((rows) => rows[0]?.count ?? 0) + : 0; + const setupDestination = + endpoint?.status === "verifying" && + enabledCount === 0 && + resource?.availability === "available"; + if ( + !reason && + !( + endpoint?.allowGroupChats && + resource?.availability === "available" && + (resource.enabled || setupDestination) + ) + ) { + reason = "Destination is not enabled in Paperclip"; + } + + if (!reason && endpoint) { + const principal = await tx + .select({ id: chatExternalPrincipals.id }) + .from(chatExternalPrincipals) + .where( + and( + eq(chatExternalPrincipals.companyId, endpoint.companyId), + eq(chatExternalPrincipals.provider, "discord"), + eq(chatExternalPrincipals.providerAccountId, event.guildId), + eq(chatExternalPrincipals.externalId, event.userId), + ), + ) + .then((rows) => rows[0] ?? null); + let allowed: boolean; + let linkedDenied = false; + if (principal) { + const authorization = await lockCurrentPrincipalAuthorization( + tx, + endpoint, + principal.id, + ); + allowed = authorization.allowed; + linkedDenied = authorization.linkedDenied; + } else if (!endpoint.allowUnlinkedPeople) { + allowed = false; + } else if (!endpoint.sponsorUserId) { + allowed = true; + } else { + const sponsor = await tx + .select({ + status: companyMemberships.status, + membershipRole: companyMemberships.membershipRole, + }) + .from(companyMemberships) + .where( + and( + eq(companyMemberships.companyId, endpoint.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, endpoint.sponsorUserId), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + allowed = + sponsor?.status === "active" && + sponsor.membershipRole !== "viewer"; + } + if (!allowed) { + reason = linkedDenied + ? "Linked Paperclip account is not currently permitted" + : endpoint.allowUnlinkedPeople + ? "Endpoint sponsor can no longer authorize external guests" + : "External identity must be linked to a Paperclip account"; + } + } + if (!reason) { + await credentialLease.assertOwned(tx); + return true; + } + + const filteredAt = new Date(); + const normalizedEvent = { + providerEventId, + kind: "mention", + trigger: "mention", + resource: { + type: "channel", + providerResourceId: event.channelId, + }, + conversation: { externalThreadId: threadId }, + message: { providerMessageId: event.messageId }, + filtering: { contentRetained: false, providerThreadCreated: false }, + }; + await notifyChatDeliveryWork(tx); + await tx + .insert(chatDeliveries) + .values({ + companyId: configuredEndpoint.companyId, + endpointId: configuredEndpoint.id, + providerEventId, + deduplicationKey: createHash("sha256") + .update(providerEventId) + .digest("hex"), + eventKind: "mention", + normalizedEvent, + state: "filtered", + redactedError: reason, + processedAt: filteredAt, + }) + .onConflictDoUpdate({ + target: [ + chatDeliveries.endpointId, + chatDeliveries.providerEventId, + ], + set: { + normalizedEvent, + principalId: null, + state: "filtered", + nextAttemptAt: null, + redactedError: reason, + processedAt: filteredAt, + updatedAt: filteredAt, + }, + }); + await tx + .update(chatEndpoints) + .set({ lastEventAt: filteredAt, updatedAt: filteredAt }) + .where(eq(chatEndpoints.id, configuredEndpoint.id)); + await credentialLease.assertOwned(tx); + return false; + }); + if (!allowed || !event.message || !context.endpointRuntime) { + return false; + } + const thread = context.endpointRuntime.thread(event.threadId); + await processMessage( + configuredEndpoint, + thread, + event.message, + "mention", + true, + null, + context, + undefined, + null, + true, + undefined, + true, + ); + const staged = await db + .select({ state: chatDeliveries.state }) + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.endpointId, configuredEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + ), + ) + .then((rows) => rows[0] ?? null); + if ( + !staged || + !["received", "retry", "processing"].includes(staged.state) + ) { + return false; + } + + await options.discordRootThreadTransportBarrier?.(); + await credentialLease.assertOwned(); + if ( + !(await ensureDiscordGatewayRuntimeIsCurrent( + configuredEndpoint.id, + context, + )) + ) { + return false; + } + try { + await context.endpointRuntime.ensureDiscordRootThread({ + channelId: event.channelId, + content: event.message.text, + messageId: event.messageId, + }); + } catch (error) { + // The durable provisional receipt owns recovery. Do not let a + // transient REST failure escape the Gateway event handler or ask the + // adapter to create the same thread outside Paperclip's lifecycle + // fence. + logger.warn( + { + endpointId: configuredEndpoint.id, + error: redactError(error), + providerEventId, + }, + "Discord root thread creation will be retried from the durable receipt", + ); + return false; + } + const promoted = await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + const current = await runtimeCallbackEndpoint( + tx, + configuredEndpoint.id, + context, + ["verifying", "active"], + ); + if (!current) return false; + await notifyChatDeliveryWork(tx); + const [row] = await tx + .update(chatDeliveries) + .set({ + state: "received", + normalizedEvent: sql`${chatDeliveries.normalizedEvent} - 'providerThreadPending'`, + nextAttemptAt: null, + redactedError: null, + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.endpointId, configuredEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + eq(chatDeliveries.state, "retry"), + sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, + ), + ) + .returning({ id: chatDeliveries.id }); + await credentialLease.assertOwned(tx); + return Boolean(row); + }); + if (promoted) + scheduleConversationDrain(configuredEndpoint.id, threadId); + // Paperclip created the provider thread inside its credential/reach + // lease. Returning false prevents the adapter from creating it again; + // the durable drain owns the remaining task mutation. + return false; + }, + ); + } + + function discordRootThreadPending( + delivery: Pick, + ): boolean { + return ( + Boolean(delivery.normalizedEvent) && + typeof delivery.normalizedEvent === "object" && + !Array.isArray(delivery.normalizedEvent) && + (delivery.normalizedEvent as { providerThreadPending?: unknown }) + .providerThreadPending === true + ); + } + + async function reconcileDiscordRootThread( + endpoint: EndpointRow, + delivery: DeliveryRow, + threadId: string, + ): Promise { + try { + const normalized = delivery.normalizedEvent as { + message?: { providerMessageId?: unknown; text?: unknown }; + resource?: { providerResourceId?: unknown }; + }; + const discordThreadParts = threadId.split(":"); + const channelId = + discordThreadParts[0] === "discord" && discordThreadParts.length >= 4 + ? discordThreadParts[2] + : normalized.resource?.providerResourceId; + const messageId = normalized.message?.providerMessageId; + if (typeof channelId !== "string" || typeof messageId !== "string") { + throw new DiscordAdapterCompatibilityError( + "the provisional root receipt is missing its channel or message id", + ); + } + const runtimeFence = lifecycleRuntimeFence(delivery); + if (!runtimeFence) { + throw new DiscordAdapterCompatibilityError( + "the provisional root receipt is missing its runtime fence", + ); + } + return await withCredentialMutationLease( + endpoint, + async (credentialLease) => { + const currentEndpoint = await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + const current = await runtimeCallbackEndpoint( + tx, + endpoint.id, + runtimeFence, + ["verifying", "active"], + ); + await credentialLease.assertOwned(tx); + return current; + }); + if (!currentEndpoint) { + throw conflict( + "Discord root thread credentials or connection state changed before recovery", + { code: "chat_endpoint_runtime_superseded" }, + ); + } + const endpointRuntime = await runtimeFor(currentEndpoint); + await credentialLease.assertOwned(); + await endpointRuntime.ensureDiscordRootThread({ + channelId, + messageId, + content: + typeof normalized.message?.text === "string" + ? normalized.message.text + : "Paperclip task", + }); + return await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + const current = await runtimeCallbackEndpoint( + tx, + endpoint.id, + runtimeFence, + ["verifying", "active"], + ); + if (!current) return false; + await notifyChatDeliveryWork(tx); + const [promoted] = await tx + .update(chatDeliveries) + .set({ + state: "received", + normalizedEvent: sql`${chatDeliveries.normalizedEvent} - 'providerThreadPending'`, + nextAttemptAt: null, + redactedError: null, + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.id, delivery.id), + eq(chatDeliveries.state, "retry"), + sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, + ), + ) + .returning({ id: chatDeliveries.id }); + await credentialLease.assertOwned(tx); + return Boolean(promoted); + }); + }, + ); + } catch (error) { + const disposition = classifyChatPublicationError( + error, + delivery.attempts + 1, + ); + const terminal = + disposition.kind === "resource_unavailable" || + disposition.kind === "failed"; + const retryAfterMs = + disposition.kind === "retry" + ? disposition.retryAfterMs + : DISCORD_ROOT_THREAD_CONFIRMATION_DELAY_MS; + const now = new Date(); + await db.transaction(async (tx) => { + if (disposition.kind === "resource_unavailable") { + const fence = lifecycleRuntimeFence(delivery); + const normalized = delivery.normalizedEvent as { + resource?: { providerResourceId?: unknown }; + }; + const providerResourceId = normalized.resource?.providerResourceId; + const current = fence + ? await runtimeCallbackEndpoint(tx, endpoint.id, fence, [ + "verifying", + "active", + "attention", + ]) + : null; + const resource = + current && typeof providerResourceId === "string" + ? await tx + .select({ id: chatEndpointResources.id }) + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.companyId, current.companyId), + eq(chatEndpointResources.endpointId, current.id), + eq( + chatEndpointResources.providerResourceId, + providerResourceId, + ), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null) + : null; + if (resource) { + await tx + .update(chatEndpointResources) + .set({ availability: "unavailable", updatedAt: now }) + .where(eq(chatEndpointResources.id, resource.id)); + await tx + .update(chatConversations) + .set({ state: "unavailable", updatedAt: now }) + .where( + and( + eq(chatConversations.companyId, current!.companyId), + eq(chatConversations.endpointId, current!.id), + eq(chatConversations.resourceId, resource.id), + inArray(chatConversations.state, ["active", "waiting"]), + ), + ); + } + } + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set({ + state: terminal ? "filtered" : "retry", + attempts: delivery.attempts + 1, + nextAttemptAt: terminal + ? null + : new Date(now.getTime() + retryAfterMs), + processedAt: terminal ? now : null, + redactedError: terminal + ? "Discord thread creation was not confirmed" + : redactError(error), + updatedAt: now, + }) + .where( + and( + eq(chatDeliveries.id, delivery.id), + eq(chatDeliveries.state, "retry"), + sql`${chatDeliveries.normalizedEvent}->>'providerThreadPending' = 'true'`, + ), + ); + }); + return false; + } + } + return { admitDiscordRootMention, discordRootThreadPending, reconcileDiscordRootThread }; +} diff --git a/server/src/services/chat-channels/inbound-attachments.ts b/server/src/services/chat-channels/inbound-attachments.ts new file mode 100644 index 00000000000..4be37251b61 --- /dev/null +++ b/server/src/services/chat-channels/inbound-attachments.ts @@ -0,0 +1,674 @@ +import type { EndpointRow, DbTransaction, DbOrTransaction, LifecycleRuntimeFence } from "./runtime-types.js"; + +import type { ChatEndpointRuntime } from "../voice/voice-runtime.js"; + +import { takePhotonCompanion } from "../photon/attachments.js"; + +import { HEIF_CONTENT_TYPES, photonHeifPreview, validatePhotonImage } from "../photon/media.js"; + +import { PhotonError } from "../photon/cloud.js"; + +import { + createHash, +} from "node:crypto"; +import { + and, + eq, + inArray, + isNull, + ne, + or, + sql, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + assets, + chatActions, + chatConversations, + chatDeliveries, + issueComments, + issueAttachments, +} from "@paperclipai/db"; + +import { + isAllowedContentType, + MAX_ATTACHMENT_BYTES, + normalizeContentType, + normalizeUploadAttachmentContentType, +} from "../../attachment-types.js"; + +import { + hasTelegramMediaProvenance, + identifyTelegramMedia, + telegramMediaNeedsIdentification, +} from "../chat-telegram-media-intake.js"; + +import { + forbidden, +} from "../../errors.js"; +import { logger } from "../../middleware/logger.js"; + +import type { StorageService } from "../../storage/types.js"; + +import { issueService } from "../issues.js"; + +import { isExternalChatWaitAuthorizationContention } from "../native-runtime/chat-attachment-reuse.js"; + +import { + GITHUB_ATTACHMENT_BATCH_TIMEOUT_MS, + GitHubAttachmentUnavailableError, + prepareGitHubPublicAttachment, +} from "../chat-github-attachments.js"; + +import type { + Attachment, +} from "chat"; + +type DeliveryRow = typeof chatDeliveries.$inferSelect; + +type InboundAttachmentsOptions = { + storage?: StorageService; + authorizeInboundWakeup: (tx: DbOrTransaction, action: typeof chatActions.$inferSelect) => Promise<{ delivery: DeliveryRow; conversation: typeof chatConversations.$inferSelect }>; + lifecycleRuntimeFence: (delivery: DeliveryRow) => LifecycleRuntimeFence | null; + sanitizeFilename: (value: string | undefined) => string | null; + issuesSvc: Pick, "createAttachment">; + isExternalActionAuthorizationChange: (error: unknown) => boolean; + redactError: (error: unknown) => string; +}; + +export function createInboundAttachments(db: Db, options: InboundAttachmentsOptions) { + const { authorizeInboundWakeup, lifecycleRuntimeFence, sanitizeFilename, issuesSvc, isExternalActionAuthorizationChange, redactError } = options; + + + async function ingestAttachments(input: { + endpoint: EndpointRow; + endpointRuntime: ChatEndpointRuntime; + deliveryId: string; + issueId: string; + issueCommentId: string; + attachments: Attachment[]; + attachmentLimitOmissions?: number; + unavailableReferenceCount?: number; + actorUserId: string | null; + }): Promise<{ + storedIds: string[]; + omissionReasons: Record; + }> { + const omissionReasons: Record = {}; + const omit = (reason: string, count = 1) => { + omissionReasons[reason] = (omissionReasons[reason] ?? 0) + count; + }; + const boundedAttachments = input.attachments.slice(0, 20); + const photonDerivatives = new Map(); + const originalPhotonAttachments = new Map(); + if ( + input.endpoint.provider === "microsoft-teams" && + input.unavailableReferenceCount + ) { + // A channel/group reference is not a download capability. Still tell + // the exact current-turn consumer that those bytes are unavailable, + // including when there are no ingestible attachments at all. + omit("download_unavailable", input.unavailableReferenceCount); + } + if ( + ["github", "imessage-photon"].includes(input.endpoint.provider) && + input.attachmentLimitOmissions + ) { + omit("attachment_limit", input.attachmentLimitOmissions); + } + if (input.attachments.length > boundedAttachments.length) { + omit( + "attachment_limit", + input.attachments.length - boundedAttachments.length, + ); + } + if (!options.storage) { + if (boundedAttachments.length) + omit("storage_unavailable", boundedAttachments.length); + return { storedIds: [], omissionReasons }; + } + const existingByFingerprint = new Map(); + const existingAttachments = await db + .select({ + id: issueAttachments.id, + byteSize: assets.byteSize, + contentType: assets.contentType, + originalFilename: assets.originalFilename, + sha256: assets.sha256, + }) + .from(issueAttachments) + .innerJoin( + assets, + and( + eq(assets.companyId, issueAttachments.companyId), + eq(assets.id, issueAttachments.assetId), + ), + ) + .where( + and( + eq(issueAttachments.companyId, input.endpoint.companyId), + eq(issueAttachments.issueId, input.issueId), + eq(issueAttachments.issueCommentId, input.issueCommentId), + ), + ); + for (const existing of existingAttachments) { + const fingerprint = JSON.stringify([ + existing.sha256, + existing.byteSize, + existing.contentType, + existing.originalFilename, + ]); + const ids = existingByFingerprint.get(fingerprint) ?? []; + ids.push(existing.id); + existingByFingerprint.set(fingerprint, ids); + } + const storedIds: string[] = []; + const withCurrentAttachmentAuthorization = async ( + work: (tx: DbTransaction) => Promise, + enforceUnchangedSource = false, + telegramMedia?: Attachment, + ): Promise => { + for (let attempt = 0; ; attempt++) { + try { + return await db.transaction(async (tx) => { + const action = await tx + .select() + .from(chatActions) + .where( + and( + eq(chatActions.companyId, input.endpoint.companyId), + eq(chatActions.endpointId, input.endpoint.id), + eq(chatActions.deliveryId, input.deliveryId), + eq(chatActions.kind, "inbound_wakeup"), + ), + ) + .limit(1) + .then((rows) => rows[0]); + if ( + !action || + !["preparing", "issued", "processing", "processed"].includes( + action.status, + ) || + action.payload.issueId !== input.issueId || + action.payload.commentId !== input.issueCommentId + ) + throw forbidden( + "The attachment's current chat admission is unavailable", + { code: "chat_action_authorization_changed" }, + ); + const current = await authorizeInboundWakeup(tx, action); + // Delivery settlement/replay can update these receipts without + // taking the issue lock. Lock in delivery -> action order only + // after policy authorization; NOWAIT breaks cross-worker lock + // inversions and the outer loop retries the whole transaction. + const lockedDelivery = await tx + .select() + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.companyId, input.endpoint.companyId), + eq(chatDeliveries.id, input.deliveryId), + ), + ) + .for("update", { noWait: true }) + .limit(1) + .then((rows) => rows[0]); + const lockedAction = await tx + .select() + .from(chatActions) + .where( + and( + eq(chatActions.companyId, input.endpoint.companyId), + eq(chatActions.id, action.id), + ), + ) + .for("update", { noWait: true }) + .limit(1) + .then((rows) => rows[0]); + const scopeKeys = [ + "version", + "issueId", + "commentId", + "agentId", + "sessionGeneration", + "requestedByActorType", + "requestedByActorId", + ] as const; + if ( + current.delivery.state !== "processing" || + !lockedDelivery || + !lockedAction || + lockedDelivery.state !== "processing" || + lockedDelivery.endpointId !== action.endpointId || + lockedDelivery.conversationId !== action.conversationId || + lockedDelivery.principalId !== action.principalId || + lockedDelivery.attempts !== current.delivery.attempts || + JSON.stringify(lifecycleRuntimeFence(lockedDelivery)) !== + JSON.stringify(lifecycleRuntimeFence(current.delivery)) || + lockedAction.endpointId !== action.endpointId || + lockedAction.deliveryId !== action.deliveryId || + lockedAction.conversationId !== action.conversationId || + lockedAction.principalId !== action.principalId || + lockedAction.kind !== "inbound_wakeup" || + !["preparing", "issued", "processing", "processed"].includes( + lockedAction.status, + ) || + scopeKeys.some( + (key) => lockedAction.payload[key] !== action.payload[key], + ) + ) + throw forbidden( + "The attachment's current chat admission is unavailable", + { code: "chat_action_authorization_changed" }, + ); + if (enforceUnchangedSource) { + if (telegramMedia) { + const normalized = lockedDelivery.normalizedEvent as { + principal?: { externalId?: string }; + message?: { + providerMessageId?: string; + attachments?: Array<{ recovery?: unknown }>; + }; + }; + const fence = lifecycleRuntimeFence(lockedDelivery); + const descriptor = + input.endpointRuntime.attachmentRecoveryDescriptor( + telegramMedia, + { + threadId: current.conversation.externalThreadId, + messageId: normalized.message?.providerMessageId ?? "", + principalExternalId: normalized.principal?.externalId, + runtimeGeneration: fence?.generation, + credentialFingerprint: fence?.credentialFingerprint, + }, + ); + if ( + descriptor?.locator.kind !== "telegram_media" || + !normalized.message?.attachments?.some((item) => { + const retained = item.recovery as { + version?: unknown; + provider?: unknown; + locator?: Record; + attachment?: Record; + } | null; + return ( + retained?.version === 1 && + retained.provider === "telegram" && + Object.entries(descriptor.locator).every( + ([key, value]) => retained.locator?.[key] === value, + ) && + Object.entries(descriptor.attachment).every( + ([key, value]) => retained.attachment?.[key] === value, + ) + ); + }) + ) + throw forbidden( + "The media's admitted source is unavailable", + { code: "chat_action_authorization_changed" }, + ); + } + const [source] = await tx + .select({ id: issueComments.id }) + .from(issueComments) + .where( + and( + eq(issueComments.companyId, input.endpoint.companyId), + eq(issueComments.issueId, input.issueId), + eq(issueComments.id, input.issueCommentId), + isNull(issueComments.deletedAt), + sql`${issueComments.updatedAt} = ${issueComments.createdAt}`, + ), + ) + .for("share", { noWait: true }); + // Lifecycle admission precedes its conversation drain. Pending + // source changes already invalidate this image download, even + // when the append-only original comment is still untouched. + const [lifecycle] = await tx + .select({ id: chatDeliveries.id }) + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.companyId, input.endpoint.companyId), + eq(chatDeliveries.endpointId, input.endpoint.id), + or( + isNull(chatDeliveries.conversationId), + eq( + chatDeliveries.conversationId, + current.conversation.id, + ), + ), + ne(chatDeliveries.state, "filtered"), + inArray(chatDeliveries.eventKind, [ + "message_updated", + "message_deleted", + "message_restored", + ]), + sql`${chatDeliveries.normalizedEvent}->'runtimeContext' = ${JSON.stringify(lifecycleRuntimeFence(lockedDelivery))}::jsonb`, + sql`${chatDeliveries.normalizedEvent}->'message'->>'targetProviderEventId' = ${lockedDelivery.providerEventId}`, + ), + ) + .limit(1); + if (!source || lifecycle) + throw forbidden( + "The image's admitted source changed before attachment registration", + { + code: "chat_action_authorization_changed", + }, + ); + } + return await work(tx); + }); + } catch (error) { + if (!isExternalChatWaitAuthorizationContention(error) || attempt >= 4) + throw error; + // Retry the complete short transaction; no network/storage work or + // backoff occurs while governance rows are locked. + await new Promise((resolve) => + setTimeout(resolve, 20 * (attempt + 1)), + ); + } + } + }; + // One shared deadline bounds the sequential batch; an expired batch never + // starts another GitHub request. Other providers retain their own policy. + const githubBatchSignal = + input.endpoint.provider === "github" + ? AbortSignal.timeout(GITHUB_ATTACHMENT_BATCH_TIMEOUT_MS) + : undefined; + // This new image-only lane shares one budget across token acquisition and + // downloads. DB/storage commit semantics remain explicit and unchanged. + const teamsInlineBatchSignal = + input.endpoint.provider === "microsoft-teams" + ? AbortSignal.timeout(10_000) + : undefined; + for (let attachment of boundedAttachments) { + const teamsInlineImage = + input.endpoint.provider === "microsoft-teams" && + input.endpointRuntime.attachmentRecoveryDescriptor(attachment)?.locator + .kind === "teams_inline_image"; + const telegramMedia = + input.endpoint.provider === "telegram" && + hasTelegramMediaProvenance(attachment); + const sourceBoundMedia = + teamsInlineImage || + telegramMedia || + input.endpoint.provider === "imessage-photon"; + const requireCurrentAttachmentAuthorization = + input.endpoint.provider === "github" || sourceBoundMedia; + try { + if (teamsInlineImage && teamsInlineBatchSignal?.aborted) { + omit("download_unavailable"); + continue; + } + // GitHub's anonymized upload URLs carry no trustworthy MIME metadata. + // Resolve public bytes only here, after durable comment admission, and + // then apply the same storage/type policy as every native attachment. + if (input.endpoint.provider === "github") { + await withCurrentAttachmentAuthorization(async () => {}); + attachment = await prepareGitHubPublicAttachment( + attachment, + githubBatchSignal, + async (request, signal) => { + await withCurrentAttachmentAuthorization(async () => {}); + return ( + (await input.endpointRuntime.resolveGitHubAttachmentComment?.( + request, + signal, + )) ?? null + ); + }, + ); + } + if ( + requireCurrentAttachmentAuthorization && + input.endpoint.provider !== "github" + ) + await withCurrentAttachmentAuthorization( + async () => {}, + sourceBoundMedia, + telegramMedia ? attachment : undefined, + ); + if ( + attachment.size !== undefined && + attachment.size > MAX_ATTACHMENT_BYTES + ) { + omit("declared_too_large"); + continue; + } + if (!attachment.fetchData) { + omit("download_unavailable"); + continue; + } + const originalFilename = sanitizeFilename(attachment.name); + let contentType = normalizeUploadAttachmentContentType({ + contentType: normalizeContentType( + attachment.mimeType ?? "application/octet-stream", + ), + originalFilename, + isAllowedContentType, + }); + // Reject a provider-declared type before allocating the downloaded + // payload. The actual byte length is checked again after the adapter's + // bounded fetch contract resolves. + // A filename may infer an Office MIME for ordinary documents, but it + // must not bypass byte identification for native media without MIME. + const identifyTelegram = + telegramMedia && + telegramMediaNeedsIdentification(attachment) && + normalizeContentType( + attachment.mimeType ?? "application/octet-stream", + ) === "application/octet-stream"; + if (!isAllowedContentType(contentType) && !identifyTelegram) { + omit("unsupported_type"); + continue; + } + const fetched = teamsInlineImage + ? await input.endpointRuntime.fetchTeamsInlineImage( + attachment, + teamsInlineBatchSignal!, + ) + : await attachment.fetchData(); + const body = Buffer.isBuffer(fetched) ? fetched : Buffer.from(fetched); + if (body.length === 0) { + omit("empty_download"); + continue; + } + if (body.length > MAX_ATTACHMENT_BYTES) { + omit("downloaded_too_large"); + continue; + } + if (identifyTelegram) { + contentType = + identifyTelegramMedia(attachment, body) ?? + "application/octet-stream"; + if (!isAllowedContentType(contentType)) { + omit("unsupported_type"); + continue; + } + } + const photonSourceHash = createHash("sha256") + .update(body) + .digest("hex"); + if (input.endpoint.provider === "imessage-photon") { + await validatePhotonImage(body, contentType); + const companion = takePhotonCompanion(body); + if (companion?.unavailable) omit("companion_unavailable"); + else if (companion) { + const video: Attachment = { type: "video", name: companion.fileName, mimeType: companion.mimeType, size: companion.data.length, fetchData: async () => companion.data }; + photonDerivatives.set(video, { source: attachment, sourceHash: photonSourceHash, kind: "live_photo_video" }); + boundedAttachments.push(video); + } + if ( + HEIF_CONTENT_TYPES.has(contentType) && + isAllowedContentType("image/jpeg") + ) { + try { + const preview = await photonHeifPreview(body); + const derivative: Attachment = { + type: "image", + name: `${originalFilename} (JPEG preview).jpg`, + mimeType: "image/jpeg", + size: preview.length, + fetchData: async () => preview, + }; + photonDerivatives.set(derivative, { source: attachment, sourceHash: photonSourceHash, kind: "heif_jpeg_preview" }); + boundedAttachments.push(derivative); + } catch { + omit("preview_unavailable"); + } + } + } + const fingerprint = JSON.stringify([ + photonSourceHash, + body.length, + contentType, + originalFilename, + ]); + const existingIds = existingByFingerprint.get(fingerprint); + const existingId = existingIds?.shift(); + if (requireCurrentAttachmentAuthorization) { + // Revoke during either API resolution or the anonymous byte download + // must prevent storage and attachment registration for that input. + await withCurrentAttachmentAuthorization( + async () => {}, + sourceBoundMedia, + telegramMedia ? attachment : undefined, + ); + } + const registerPhotonProvenance = async (attachmentId: string) => { + if (input.endpoint.provider !== "imessage-photon") return; + const relation = photonDerivatives.get(attachment); + if (!relation) { + originalPhotonAttachments.set(attachment, attachmentId); + return; + } + const originalAttachmentId = + originalPhotonAttachments.get(relation.source); + if (!originalAttachmentId) + throw new Error("Photon related attachment source is missing"); + await db + .insert(chatActions) + .values({ + companyId: input.endpoint.companyId, + endpointId: input.endpoint.id, + deliveryId: input.deliveryId, + kind: relation.kind === "heif_jpeg_preview" ? "attachment_derivative" : "attachment_companion", + providerActionId: `photon-${relation.kind}:${input.deliveryId}:${originalAttachmentId}`, + payload: { + originalAttachmentId, + derivativeAttachmentId: attachmentId, + originalSha256: relation.sourceHash, + derivativeSha256: photonSourceHash, + kind: relation.kind, + }, + status: "processed", + }) + .onConflictDoNothing(); + }; + if (existingId) { + storedIds.push(existingId); + await registerPhotonProvenance(existingId); + continue; + } + const stored = await options.storage.putFile({ + companyId: input.endpoint.companyId, + namespace: `issues/${input.issueId}`, + originalFilename, + contentType, + body, + }); + const registration = { + issueId: input.issueId, + issueCommentId: input.issueCommentId, + provider: stored.provider, + objectKey: stored.objectKey, + contentType: stored.contentType, + byteSize: stored.byteSize, + sha256: stored.sha256, + originalFilename: stored.originalFilename, + createdByUserId: input.actorUserId, + }; + let row; + try { + row = requireCurrentAttachmentAuthorization + ? await withCurrentAttachmentAuthorization( + (tx) => + issueService(tx as unknown as Db).createAttachment( + registration, + ), + sourceBoundMedia, + telegramMedia ? attachment : undefined, + ) + : await issuesSvc.createAttachment(registration); + } catch (error) { + // An explicit authorization denial happens before registration. Do + // not delete on ambiguous database errors which may have committed. + if ( + requireCurrentAttachmentAuthorization && + isExternalActionAuthorizationChange(error) + ) + await options.storage + .deleteObject(input.endpoint.companyId, stored.objectKey) + .catch(() => undefined); + throw error; + } + storedIds.push(row.id); + await registerPhotonProvenance(row.id); + } catch (error) { + if (isExternalActionAuthorizationChange(error)) throw error; + if ( + error instanceof PhotonError && + ["attachment_not_ready", "network", "quota", "credentials"].includes( + error.code, + ) + ) + throw error; + // Use the closed current-input omission vocabulary consumed by native + // prompts; provider-specific diagnostics remain redacted log codes. + omit( + error instanceof GitHubAttachmentUnavailableError + ? "download_unavailable" + : "processing_failed", + ); + // A malformed or unavailable provider attachment must not strand the + // durable text delivery. The rejected file is intentionally omitted; + // the delivery remains auditable through its normalized attachment + // metadata without persisting provider credentials or download URLs. + logger.warn( + { + endpointId: input.endpoint.id, + issueId: input.issueId, + ...(error instanceof GitHubAttachmentUnavailableError + ? { + deliveryId: input.deliveryId, + attachmentDiagnosticCode: error.code, + } + : {}), + attachmentName: sanitizeFilename(attachment.name), + error: redactError(error), + }, + "external chat attachment was rejected", + ); + } + } + return { storedIds, omissionReasons }; + } + + function attachmentOmissionDetail(result: { + omissionReasons: Record; + }): string | null { + const entries = Object.entries(result.omissionReasons).filter( + ([, count]) => count > 0, + ); + const omitted = entries.reduce((total, [, count]) => total + count, 0); + if (!omitted) return null; + const reasons = entries + .map(([reason, count]) => `${reason.replaceAll("_", " ")}: ${count}`) + .join(", "); + return `${omitted} external attachment${omitted === 1 ? " was" : "s were"} omitted (${reasons})`; + } + return { ingestAttachments, attachmentOmissionDetail }; +} diff --git a/server/src/services/chat-channels/microsoft-teams/file-transfer-projection.ts b/server/src/services/chat-channels/microsoft-teams/file-transfer-projection.ts new file mode 100644 index 00000000000..34365a46184 --- /dev/null +++ b/server/src/services/chat-channels/microsoft-teams/file-transfer-projection.ts @@ -0,0 +1,18 @@ +import { sql } from "drizzle-orm"; +import { chatTeamsFileTransfers } from "@paperclipai/db"; + +export const fileTransferProjectionColumns = { + publicationId: chatTeamsFileTransfers.publicationId, + phase: chatTeamsFileTransfers.phase, + version: chatTeamsFileTransfers.version, + filename: chatTeamsFileTransfers.filename, + expiresAt: chatTeamsFileTransfers.expiresAt, + consentMessageId: chatTeamsFileTransfers.consentMessageId, + responseActivityId: chatTeamsFileTransfers.responseActivityId, + fileInfoMessageId: chatTeamsFileTransfers.fileInfoMessageId, + operatorConfirmed: sql` + coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,schema}', '') = 'paperclip.teams.file-resolution.v1' + and coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,action}', '') = 'mark_delivered' + and coalesce(${chatTeamsFileTransfers.privateState}#>>'{resolution,fromPhase}', '') = 'file_info_unknown' + `, +}; diff --git a/server/src/services/chat-channels/outbound-attachments.ts b/server/src/services/chat-channels/outbound-attachments.ts new file mode 100644 index 00000000000..0f2e3a8b02e --- /dev/null +++ b/server/src/services/chat-channels/outbound-attachments.ts @@ -0,0 +1,294 @@ +import { + createHash, +} from "node:crypto"; +import { + and, + eq, + inArray, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + assets, + chatPublications, + issueAttachments, +} from "@paperclipai/db"; +import type { + SafeChatPublicationPayload, +} from "@paperclipai/shared"; + +import { + isAllowedContentType, + MAX_ATTACHMENT_BYTES, +} from "../../attachment-types.js"; + +import type { StorageService } from "../../storage/types.js"; + +import { + isTeamsInlineImageContentType, + TEAMS_INLINE_IMAGE_MAX_BYTES, +} from "../chat-teams-inline-images.js"; + +import type { + FileUpload, +} from "chat"; + +const OUTBOUND_ATTACHMENT_STORAGE_TIMEOUT_MS = 10_000; + +class OutboundAttachmentHydrationError extends Error { + readonly code = "CHAT_ATTACHMENT_HYDRATION_FAILED"; + + constructor(message: string, cause?: unknown) { + super(message, cause === undefined ? undefined : { cause }); + this.name = "OutboundAttachmentHydrationError"; + } +} + +class OutboundAttachmentValidationError extends Error { + readonly code = "CHAT_ATTACHMENT_VALIDATION_FAILED"; + + constructor(message: string) { + super(message); + this.name = "OutboundAttachmentValidationError"; + } +} + +function isOutboundAttachmentHydrationError( + error: unknown, +): error is OutboundAttachmentHydrationError { + return error instanceof OutboundAttachmentHydrationError; +} + +function isOutboundAttachmentValidationError( + error: unknown, +): error is OutboundAttachmentValidationError { + return error instanceof OutboundAttachmentValidationError; +} + +/** + * Read one already-authorized attachment before any provider transport starts. + * The database digest, rather than mutable object-store metadata, is the + * integrity boundary. Both acquisition and streaming are bounded so a stuck + * storage backend cannot hold the publication lane indefinitely. + */ +export async function hydrateOutboundAttachment(input: { + storage: StorageService; + companyId: string; + objectKey: string; + byteSize: number; + sha256: string; + filename: string; + mimeType: string; + timeoutMs?: number; +}): Promise { + const timeoutMs = + typeof input.timeoutMs === "number" && + Number.isFinite(input.timeoutMs) && + input.timeoutMs > 0 + ? input.timeoutMs + : OUTBOUND_ATTACHMENT_STORAGE_TIMEOUT_MS; + if ( + !Number.isSafeInteger(input.byteSize) || + input.byteSize <= 0 || + input.byteSize > MAX_ATTACHMENT_BYTES || + !/^[0-9a-f]{64}$/iu.test(input.sha256) + ) { + throw new OutboundAttachmentHydrationError( + "Chat publication attachment metadata is invalid", + ); + } + const acquisitionTimeoutError = new OutboundAttachmentHydrationError( + "Chat publication attachment storage read timed out", + ); + let acquisitionTimedOut = false; + let rejectAcquisition!: (error: Error) => void; + const acquisitionTimeout = new Promise((_resolve, reject) => { + rejectAcquisition = reject; + }); + const acquisitionTimer = setTimeout(() => { + acquisitionTimedOut = true; + rejectAcquisition(acquisitionTimeoutError); + }, timeoutMs); + acquisitionTimer.unref?.(); + const objectPromise = Promise.resolve() + .then(() => input.storage.getObject(input.companyId, input.objectKey)) + .then((object) => { + if (acquisitionTimedOut) object.stream.destroy(); + return object; + }) + .catch((error) => { + throw isOutboundAttachmentHydrationError(error) + ? error + : new OutboundAttachmentHydrationError( + "Chat publication attachment storage read failed", + error, + ); + }); + let object: Awaited>; + try { + object = await Promise.race([objectPromise, acquisitionTimeout]); + } finally { + clearTimeout(acquisitionTimer); + } + + if ( + object.contentLength !== undefined && + object.contentLength !== input.byteSize + ) { + object.stream.destroy(); + throw new OutboundAttachmentHydrationError( + "Chat publication attachment size changed after registration", + ); + } + + const streamTimeoutError = new OutboundAttachmentHydrationError( + "Chat publication attachment storage read timed out", + ); + const streamTimer = setTimeout(() => { + object.stream.destroy(streamTimeoutError); + }, timeoutMs); + streamTimer.unref?.(); + const chunks: Buffer[] = []; + const digest = createHash("sha256"); + let bytes = 0; + try { + for await (const chunk of object.stream) { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + bytes += buffer.length; + if (bytes > input.byteSize || bytes > MAX_ATTACHMENT_BYTES) { + object.stream.destroy(); + throw new OutboundAttachmentHydrationError( + "Chat publication attachment exceeded its streaming byte limit", + ); + } + digest.update(buffer); + chunks.push(buffer); + } + } catch (error) { + throw isOutboundAttachmentHydrationError(error) + ? error + : new OutboundAttachmentHydrationError( + "Chat publication attachment storage read failed", + error, + ); + } finally { + clearTimeout(streamTimer); + } + + const actualDigest = digest.digest("hex"); + if (bytes !== input.byteSize || actualDigest !== input.sha256.toLowerCase()) { + if (!object.stream.destroyed) object.stream.destroy(); + throw new OutboundAttachmentHydrationError( + "Chat publication attachment integrity changed after registration", + ); + } + + return { + data: Buffer.concat(chunks, bytes), + filename: input.filename, + mimeType: input.mimeType, + }; +} +export { isOutboundAttachmentHydrationError, isOutboundAttachmentValidationError }; + +type OutboundAttachmentsOptions = { storage?: StorageService; }; + +export function createOutboundAttachments(db: Db, options: OutboundAttachmentsOptions) { + + + + async function publicationFiles( + publication: typeof chatPublications.$inferSelect, + payload: SafeChatPublicationPayload, + preparation: { teamsInlineImagesOnly?: boolean } = {}, + ): Promise { + if (!payload.attachmentIds?.length) return []; + try { + const rows = await db + .select({ + id: issueAttachments.id, + issueId: issueAttachments.issueId, + issueCommentId: issueAttachments.issueCommentId, + objectKey: assets.objectKey, + contentType: assets.contentType, + byteSize: assets.byteSize, + sha256: assets.sha256, + originalFilename: assets.originalFilename, + }) + .from(issueAttachments) + .innerJoin(assets, eq(issueAttachments.assetId, assets.id)) + .where( + and( + eq(issueAttachments.companyId, publication.companyId), + eq(issueAttachments.issueId, publication.issueId), + inArray(issueAttachments.id, payload.attachmentIds), + ), + ); + const byId = new Map(rows.map((row) => [row.id, row])); + const orderedRows = payload.attachmentIds.map((attachmentId) => { + const row = byId.get(attachmentId); + if ( + !row || + (publication.commentId && + row.issueCommentId !== publication.commentId) || + row.byteSize <= 0 || + row.byteSize > MAX_ATTACHMENT_BYTES || + !row.objectKey.trim() || + !/^[0-9a-f]{64}$/iu.test(row.sha256) || + !isAllowedContentType(row.contentType) + ) + throw new OutboundAttachmentValidationError( + "Chat publication attachment is invalid or outside its authorized task comment", + ); + return row; + }); + // Picture messages have a different Teams contract from arbitrary files. + // Preserve the normal scope/metadata checks above, but never hydrate a + // document or oversized image just to discover it needs a task link. + if ( + preparation.teamsInlineImagesOnly && + orderedRows.some( + (row) => + row.byteSize > TEAMS_INLINE_IMAGE_MAX_BYTES || + !isTeamsInlineImageContentType(row.contentType), + ) + ) { + return []; + } + // Authorization and persisted metadata must fail definitively even when + // storage is unavailable; do not mask an invalid file as a transient outage. + if (!options.storage) + throw new OutboundAttachmentHydrationError( + "Attachment storage is unavailable for chat publication", + ); + const files: FileUpload[] = []; + for (const row of orderedRows) { + files.push( + await hydrateOutboundAttachment({ + storage: options.storage, + companyId: publication.companyId, + objectKey: row.objectKey, + byteSize: row.byteSize, + sha256: row.sha256, + filename: row.originalFilename ?? `attachment-${row.id}`, + mimeType: row.contentType, + }), + ); + } + return files; + } catch (error) { + if ( + isOutboundAttachmentHydrationError(error) || + isOutboundAttachmentValidationError(error) + ) { + throw error; + } + throw new OutboundAttachmentHydrationError( + "Chat publication attachment preparation failed", + error, + ); + } + } + return { publicationFiles }; +} diff --git a/server/src/services/chat-channels/provider-effects.ts b/server/src/services/chat-channels/provider-effects.ts new file mode 100644 index 00000000000..5ca3b4b1786 --- /dev/null +++ b/server/src/services/chat-channels/provider-effects.ts @@ -0,0 +1,957 @@ +import { createTelegramCallbackNoticeAuthority } from "./telegram/callback-notices.js"; + +import { slackThreadChannelId } from "./slack/messages.js"; + +import type { EndpointRow, DbTransaction, DbOrTransaction, LifecycleRuntimeFence } from "./runtime-types.js"; + +import { baseTeamsConversationId } from "./microsoft-teams/resources.js"; + +import { dueChatAction, PROVIDER_EFFECT_STALE_MS } from "../chat-action-work.js"; + +import { notifyChatDeliveryWork, notifyChatActionWork } from "../chat-work-notifications.js"; + +import type { ChatEndpointRuntime } from "../voice/voice-runtime.js"; + +import { chatCredentialMutationLease } from "../chat-credential-mutation-lease.js"; + +import { projectSafeChatPublicationText } from "../chat-publication-projection.js"; + +import { PhotonChatAdapter } from "../photon/adapter.js"; + +import { + and, + asc, + eq, + inArray, + lte, + sql, +} from "drizzle-orm"; + +import { + teamsConversationId, +} from "../chat-control-chronology.js"; + +import type { Db } from "@paperclipai/db"; + +import { + chatActions, + chatConversations, + chatDeliveries, + chatEndpointResources, + chatEndpoints, + toolConnections, +} from "@paperclipai/db"; +import type { + ChatProvider, +} from "@paperclipai/shared"; + +import { + parseTelegramCallbackReceipt, + telegramCallbackThreadId, + TELEGRAM_PRIVATE_ACTION_UNAVAILABLE, + type TelegramCallbackReceipt, +} from "../chat-telegram-ephemeral.js"; + +import { logger } from "../../middleware/logger.js"; +import { redactSensitiveText } from "../../redaction.js"; + +import { classifyChatPublicationError } from "../chat-publication-errors.js"; + +import type { + Thread, +} from "chat"; + +import type { EndpointRecord } from "./runtime-types.js"; + +type ProviderEffectTarget = { + post: Thread["post"]; + postEphemeral?: Thread["postEphemeral"]; +}; +type ProviderEffectPayload = { + version: 1; + effect: "thread_message" | "ephemeral_message" | "telegram_callback_notice"; + telegramCallback?: TelegramCallbackReceipt; + telegramAuthorizationSha256?: string; + authorizationMode?: "principal" | "safe_notice"; + threadId: string; + userId?: string; + text: string; + fallbackText?: string; + settleDelivery: boolean; + completeConversationId?: string; + resourceId?: string; + runtimeGeneration: number; + credentialFingerprint: string; +}; + +function providerEffectPayload( + payload: Record, +): ProviderEffectPayload | null { + if ( + payload.version !== 1 || + (payload.effect !== "thread_message" && + payload.effect !== "ephemeral_message" && + payload.effect !== "telegram_callback_notice") || + typeof payload.threadId !== "string" || + !payload.threadId || + typeof payload.text !== "string" || + !payload.text || + typeof payload.settleDelivery !== "boolean" || + typeof payload.runtimeGeneration !== "number" || + !Number.isSafeInteger(payload.runtimeGeneration) || + typeof payload.credentialFingerprint !== "string" + ) { + return null; + } + if ( + payload.effect === "ephemeral_message" && + (typeof payload.userId !== "string" || !payload.userId) + ) { + return null; + } + for (const key of [ + "authorizationMode", + "userId", + "fallbackText", + "completeConversationId", + "resourceId", + ] as const) { + const value = payload[key]; + if (value !== undefined && (typeof value !== "string" || !value)) { + return null; + } + } + if ( + payload.authorizationMode !== undefined && + payload.authorizationMode !== "principal" && + payload.authorizationMode !== "safe_notice" + ) { + return null; + } + if (payload.effect === "telegram_callback_notice") { + const receipt = parseTelegramCallbackReceipt(payload.telegramCallback); + if ( + !receipt || + payload.authorizationMode !== "safe_notice" || + payload.text !== TELEGRAM_PRIVATE_ACTION_UNAVAILABLE || + payload.threadId !== telegramCallbackThreadId(receipt) || + payload.userId !== receipt.receiverUserId || + payload.settleDelivery !== false || + payload.fallbackText !== undefined || + payload.completeConversationId !== undefined || + typeof payload.telegramAuthorizationSha256 !== "string" || + !/^[a-f0-9]{64}$/.test(payload.telegramAuthorizationSha256) + ) + return null; + } else if ( + payload.telegramCallback !== undefined || + payload.telegramAuthorizationSha256 !== undefined + ) + return null; + return payload as ProviderEffectPayload; +} +export type { ProviderEffectTarget, ProviderEffectPayload }; +export { providerEffectPayload }; + +type ProviderEffectsOptions = { + endpointRecord: (endpointId: string) => Promise; + runtimeCallbackEndpoint: (tx: DbTransaction, endpointId: string, context: LifecycleRuntimeFence, statuses: EndpointRow["status"][]) => Promise; + withCredentialMutationLease: ReturnType; + runtimeFor: (endpoint: EndpointRow) => Promise; + invalidateRuntime: (endpointId: string) => Promise; + lockCurrentPrincipalAuthorization: (tx: DbOrTransaction, endpoint: EndpointRow, principalId: string) => Promise<{ allowed: boolean }>; + redactError: (error: unknown) => string; + MAX_ERROR_TEXT: number; + nonDirectDestinationAllowed: (endpoint: EndpointRow, resource: typeof chatEndpointResources.$inferSelect | null | undefined) => boolean; + lockTelegramCallbackNoticeAuthority: ReturnType["lockTelegramCallbackNoticeAuthority"]; +}; + +export function createProviderEffects(db: Db, options: ProviderEffectsOptions) { + const { endpointRecord, runtimeCallbackEndpoint, withCredentialMutationLease, runtimeFor, invalidateRuntime, lockCurrentPrincipalAuthorization, redactError, MAX_ERROR_TEXT, nonDirectDestinationAllowed, lockTelegramCallbackNoticeAuthority } = options; + + + async function stageProviderEffect( + database: DbOrTransaction, + input: { + endpoint: EndpointRow; + deliveryId?: string | null; + conversationId?: string | null; + principalId?: string | null; + providerActionId: string; + payload: Omit< + ProviderEffectPayload, + "runtimeGeneration" | "credentialFingerprint" + >; + runtimeContext: LifecycleRuntimeFence; + }, + ) { + await notifyChatActionWork(database, "provider_effect"); + const [inserted] = await database + .insert(chatActions) + .values({ + companyId: input.endpoint.companyId, + endpointId: input.endpoint.id, + deliveryId: input.deliveryId ?? null, + conversationId: input.conversationId ?? null, + principalId: input.principalId ?? null, + kind: "provider_effect", + providerActionId: input.providerActionId, + payload: { + ...input.payload, + runtimeGeneration: input.runtimeContext.generation, + credentialFingerprint: input.runtimeContext.credentialFingerprint, + }, + status: "received", + }) + .onConflictDoNothing() + .returning(); + if (inserted) return inserted; + return database + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, input.endpoint.id), + eq(chatActions.providerActionId, input.providerActionId), + eq(chatActions.kind, "provider_effect"), + ), + ) + .then((rows) => rows[0] ?? null); + } + + function providerEffectDisposition( + error: unknown, + attempt: number, + providerEffectCompleted: boolean, + ) { + if (!providerEffectCompleted) + return classifyChatPublicationError(error, attempt); + return { + kind: "delivery_unknown" as const, + reason: + "Provider effect completed, but Paperclip could not confirm its durable result", + }; + } + + async function finalizeProviderEffectFailure(input: { + action: typeof chatActions.$inferSelect; + endpoint: EndpointRow; + payload: ProviderEffectPayload; + disposition: ReturnType; + attempt: number; + }): Promise { + const failure = redactSensitiveText(input.disposition.reason).slice( + 0, + MAX_ERROR_TEXT, + ); + const retryable = input.disposition.kind === "retry"; + const retryAt = + input.disposition.kind === "retry" + ? new Date(Date.now() + input.disposition.retryAfterMs) + : null; + let invalidateCurrentRuntime = false; + const finalized = await db.transaction(async (tx) => { + // Admission locks the endpoint before its delivery. Keep that order + // when failure settlement also needs to change endpoint/resource health. + const currentEndpoint = + input.disposition.kind === "endpoint_attention" || + input.disposition.kind === "resource_unavailable" + ? await runtimeCallbackEndpoint( + tx, + input.endpoint.id, + { + generation: input.payload.runtimeGeneration, + credentialFingerprint: input.payload.credentialFingerprint, + }, + ["verifying", "active"], + ) + : null; + await notifyChatActionWork(tx, "provider_effect"); + const [ownedAction] = await tx + .update(chatActions) + .set({ + status: + input.disposition.kind === "delivery_unknown" + ? "delivery_unknown" + : "failed", + result: { + code: `provider_effect_${input.disposition.kind}`, + attempts: input.attempt, + redactedError: failure, + retryable, + retryAt: retryAt?.toISOString() ?? null, + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, input.action.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${input.attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (!ownedAction) return false; + if (input.payload.settleDelivery && input.action.deliveryId) { + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set({ + state: retryable ? "retry" : "failed", + nextAttemptAt: retryAt, + redactedError: + input.disposition.kind === "delivery_unknown" + ? `Provider response is unconfirmed: ${failure}`.slice( + 0, + MAX_ERROR_TEXT, + ) + : failure, + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.id, input.action.deliveryId), + eq(chatDeliveries.state, "processing"), + ), + ); + } + if (input.disposition.kind === "endpoint_attention" && currentEndpoint) { + await tx + .update(chatEndpoints) + .set({ + status: "attention", + healthMessage: "Provider credentials or permissions need attention", + lastError: failure, + updatedAt: new Date(), + }) + .where(eq(chatEndpoints.id, currentEndpoint.id)); + await tx + .update(toolConnections) + .set({ + status: "disabled", + enabled: false, + healthStatus: "degraded", + healthMessage: "Provider credentials or permissions need attention", + lastError: failure, + healthCheckedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(toolConnections.id, currentEndpoint.connectionId)); + invalidateCurrentRuntime = true; + } else if ( + input.disposition.kind === "resource_unavailable" && + input.payload.resourceId && + currentEndpoint + ) { + await tx + .update(chatEndpointResources) + .set({ availability: "unavailable", updatedAt: new Date() }) + .where( + and( + eq(chatEndpointResources.id, input.payload.resourceId), + eq(chatEndpointResources.endpointId, currentEndpoint.id), + ), + ); + } + return true; + }); + if (finalized && invalidateCurrentRuntime) { + await invalidateRuntime(input.endpoint.id).catch(() => undefined); + } + return finalized; + } + + async function quarantineStaleProviderEffect( + action: typeof chatActions.$inferSelect, + ) { + const payload = providerEffectPayload(action.payload); + const failure = + "Provider delivery could not be confirmed after the worker stopped. The effect will not be replayed automatically."; + await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "provider_effect"); + const [quarantined] = await tx + .update(chatActions) + .set({ + status: "delivery_unknown", + result: { + code: "provider_effect_delivery_unknown", + redactedError: failure, + retryable: false, + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "processing"), + eq(chatActions.updatedAt, action.updatedAt), + sql`(${chatActions.result}->>'attempts')::int = ${ + typeof action.result?.attempts === "number" + ? action.result.attempts + : -1 + }`, + lte( + chatActions.updatedAt, + new Date(Date.now() - PROVIDER_EFFECT_STALE_MS), + ), + ), + ) + .returning({ id: chatActions.id }); + if (quarantined && payload?.settleDelivery && action.deliveryId) { + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set({ + state: "failed", + nextAttemptAt: null, + redactedError: failure, + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.state, "processing"), + ), + ); + } + }); + } + + function providerEffectThreadResourceId( + provider: ChatProvider, + threadId: string, + ): string | null { + if (provider === "slack") { + return ( + slackThreadChannelId(threadId) ?? + (!threadId.includes(":") ? threadId : null) + ); + } + if (provider === "telegram") { + return /^telegram:([^:]+)/.exec(threadId)?.[1] ?? null; + } + if (provider === "github") { + return /^github:([^:]+)/.exec(threadId)?.[1]?.toLowerCase() ?? null; + } + if (provider === "discord") { + return /^discord:[^:]+:([^:]+)/.exec(threadId)?.[1] ?? null; + } + const conversationId = teamsConversationId(threadId); + return conversationId ? baseTeamsConversationId(conversationId) : null; + } + + async function lockCurrentProviderEffectAuthorization( + tx: DbTransaction, + action: typeof chatActions.$inferSelect, + endpoint: EndpointRow, + payload: ProviderEffectPayload, + ): Promise { + if (payload.effect === "telegram_callback_notice") { + const receipt = parseTelegramCallbackReceipt(payload.telegramCallback); + if (!receipt || !action.deliveryId) return false; + const delivery = await tx + .select() + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.companyId, action.companyId), + eq(chatDeliveries.endpointId, action.endpointId), + eq(chatDeliveries.state, "filtered"), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + const retained = + delivery && + parseTelegramCallbackReceipt(delivery.normalizedEvent.telegramCallback); + if ( + !delivery || + delivery.principalId !== action.principalId || + retained?.sourceSha256 !== receipt.sourceSha256 || + delivery.normalizedEvent.telegramAuthorizationSha256 !== + payload.telegramAuthorizationSha256 + ) + return false; + return ( + (await lockTelegramCallbackNoticeAuthority( + tx, + endpoint, + receipt, + action.principalId, + )) === payload.telegramAuthorizationSha256 + ); + } + const conversation = action.conversationId + ? await tx + .select() + .from(chatConversations) + .where( + and( + eq(chatConversations.companyId, endpoint.companyId), + eq(chatConversations.endpointId, endpoint.id), + eq(chatConversations.id, action.conversationId), + inArray(chatConversations.state, ["active", "waiting"]), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null) + : null; + if (action.conversationId && !conversation) return false; + const inferredResourceId = providerEffectThreadResourceId( + endpoint.provider, + payload.threadId, + ); + const resource = await tx + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.companyId, endpoint.companyId), + eq(chatEndpointResources.endpointId, endpoint.id), + payload.resourceId || conversation?.resourceId + ? eq( + chatEndpointResources.id, + payload.resourceId ?? conversation!.resourceId!, + ) + : inferredResourceId + ? eq(chatEndpointResources.providerResourceId, inferredResourceId) + : sql`false`, + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + const slackChannelId = + endpoint.provider === "slack" + ? slackThreadChannelId(payload.threadId) + : null; + const isDirectMessage = + conversation?.isDirectMessage === true || + resource?.type === "direct_message" || + Boolean(slackChannelId && /^D[A-Z0-9]+$/i.test(slackChannelId)); + const destinationAllowed = isDirectMessage + ? endpoint.allowDirectMessages + : nonDirectDestinationAllowed(endpoint, resource); + if (payload.authorizationMode === "safe_notice") return true; + if (!destinationAllowed) return false; + if (!action.principalId) return false; + return ( + await lockCurrentPrincipalAuthorization(tx, endpoint, action.principalId) + ).allowed; + } + + async function cancelUnauthorizedProviderEffect( + tx: DbTransaction, + action: typeof chatActions.$inferSelect, + attempt: number, + ): Promise { + await notifyChatActionWork(tx, "provider_effect"); + const [cancelled] = await tx + .update(chatActions) + .set({ + status: "cancelled", + result: { + attempts: attempt, + code: "provider_effect_no_longer_authorized", + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (!cancelled) return false; + if (action.deliveryId) { + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set({ + state: "filtered", + processedAt: new Date(), + nextAttemptAt: null, + redactedError: + "Provider reply was suppressed because current chat authorization changed", + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.state, "processing"), + ), + ); + } + return true; + } + + async function processProviderEffect( + actionId: string, + liveTarget?: ProviderEffectTarget, + ): Promise<"processed" | "pending" | "failed" | "delivery_unknown"> { + const initial = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.id, actionId), + eq(chatActions.kind, "provider_effect"), + ), + ) + .then((rows) => rows[0] ?? null); + if (!initial) return "failed"; + const initialEndpoint = await endpointRecord(initial.endpointId); + if (!initialEndpoint) return "failed"; + + const processClaimedEffect = async () => { + let action = await db + .select() + .from(chatActions) + .where(eq(chatActions.id, actionId)) + .then((rows) => rows[0] ?? null); + if (!action) return "failed"; + const payload = providerEffectPayload(action.payload); + if (!payload) { + await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "provider_effect"); + return tx + .update(chatActions) + .set({ + status: "failed", + result: { code: "provider_effect_payload_invalid" }, + updatedAt: new Date(), + }) + .where(eq(chatActions.id, action.id)); + }); + return "failed"; + } + if (action.status === "processed") return "processed"; + if (action.status === "delivery_unknown") return "delivery_unknown"; + if (action.status === "processing") { + if (action.updatedAt > new Date(Date.now() - PROVIDER_EFFECT_STALE_MS)) + return "pending"; + await quarantineStaleProviderEffect(action); + return "delivery_unknown"; + } + if (action.status === "failed") { + if (action.result?.retryable !== true) return "failed"; + const retryAt = + typeof action.result.retryAt === "string" + ? new Date(action.result.retryAt) + : null; + if (retryAt && retryAt > new Date()) return "pending"; + } else if (action.status !== "received") { + return "failed"; + } + + const attempt = + (typeof action.result?.attempts === "number" + ? action.result.attempts + : 0) + 1; + const record = await endpointRecord(action.endpointId); + let providerEffectCompleted = false; + try { + if (!record) { + throw Object.assign( + new Error("Chat endpoint is unavailable for this provider effect"), + { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, + ); + } + return await withCredentialMutationLease( + record.endpoint, + async (credentialLease) => { + const claim = await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + const currentAction = await tx + .select() + .from(chatActions) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.kind, "provider_effect"), + eq(chatActions.status, action!.status), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + if (!currentAction) return null; + const currentEndpoint = await runtimeCallbackEndpoint( + tx, + currentAction.endpointId, + { + generation: payload.runtimeGeneration, + credentialFingerprint: payload.credentialFingerprint, + }, + ["verifying", "active"], + ); + const authorized = currentEndpoint + ? await lockCurrentProviderEffectAuthorization( + tx, + currentAction, + currentEndpoint, + payload, + ) + : false; + await notifyChatActionWork(tx, "provider_effect"); + const [transportClaim] = await tx + .update(chatActions) + .set({ + status: "processing", + result: { attempts: attempt }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, currentAction.id), + eq(chatActions.status, currentAction.status), + ), + ) + .returning(); + if (!transportClaim) return null; + if (!currentEndpoint || !authorized) { + const cancelled = await cancelUnauthorizedProviderEffect( + tx, + transportClaim, + attempt, + ); + if (!cancelled) { + throw new Error( + "Provider effect ownership changed before authorization cancellation", + ); + } + return { outcome: "cancelled" as const }; + } + await credentialLease.assertOwned(tx); + return { + action: transportClaim, + endpoint: currentEndpoint, + outcome: "claimed" as const, + }; + }); + if (!claim) return "pending"; + if (claim.outcome === "cancelled") return "failed"; + action = claim.action; + + // `processing` is the durable, single-owner transport claim and + // authorization linearization point. Its transaction has committed + // before provider I/O begins. The renewable credential lease keeps + // runtime identity stable without holding database row locks. + let sent: { id: string; threadId: string } | null = null; + if (payload.effect === "telegram_callback_notice") { + sent = await ( + await runtimeFor(claim.endpoint) + ).sendTelegramCallbackNotice( + payload.telegramCallback!, + payload.text, + ); + } else { + const target = + liveTarget ?? + (await runtimeFor(claim.endpoint)).thread(payload.threadId); + if (payload.effect === "ephemeral_message") { + if (!target.postEphemeral) { + if (!payload.fallbackText) { + throw Object.assign( + new Error("Provider does not support ephemeral messages"), + { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, + ); + } + sent = await target.post(payload.fallbackText); + } else { + try { + sent = await target.postEphemeral( + payload.userId!, + payload.text, + { fallbackToDM: false }, + ); + } catch (error) { + if ( + !payload.fallbackText || + classifyChatPublicationError(error, attempt).kind !== + "failed" + ) { + throw error; + } + } + if (!sent && payload.fallbackText) { + sent = await target.post(payload.fallbackText); + } + } + } else if (claim.endpoint.provider === "imessage-photon") { + const adapter = (await runtimeFor(claim.endpoint)).getProviderAdapter(); + if (!(adapter instanceof PhotonChatAdapter)) throw new Error("Photon runtime unavailable"); + const receipt = await adapter.publish(payload.threadId, `effect:${action.id}`, projectSafeChatPublicationText(payload.text), { + assertCurrent: async () => { await credentialLease.assertOwned(); }, + }); + sent = { id: receipt.id, threadId: payload.threadId }; + } else { + sent = await target.post(payload.text); + } + } + if (!sent) { + throw Object.assign( + new Error("Provider did not accept the message"), + { code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED" }, + ); + } + providerEffectCompleted = true; + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + // A duplicate webhook holds the endpoint while waiting for the + // delivery's unique-index entry. Lock this endpoint first too, + // before settling the action/conversation/delivery, so neither + // transaction can wait on a row already held by the other. + const currentEndpoint = await runtimeCallbackEndpoint( + tx, + claim.endpoint.id, + { + generation: payload.runtimeGeneration, + credentialFingerprint: payload.credentialFingerprint, + }, + ["verifying", "active"], + ); + await notifyChatActionWork(tx, "provider_effect"); + const [completed] = await tx + .update(chatActions) + .set({ + status: "processed", + result: { + attempts: attempt, + providerMessageId: sent.id, + threadId: sent.threadId, + ...(payload.effect === "telegram_callback_notice" + ? { + code: + payload.telegramCallback?.chatType === "private" + ? "telegram_private_api_accepted" + : "telegram_ephemeral_api_accepted", + } + : {}), + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (!completed) { + throw new Error( + "Provider effect ownership changed before commit", + ); + } + if (payload.completeConversationId) { + await tx + .update(chatConversations) + .set({ state: "completed", updatedAt: new Date() }) + .where( + and( + eq(chatConversations.id, payload.completeConversationId), + eq(chatConversations.endpointId, claim.endpoint.id), + inArray(chatConversations.state, ["active", "waiting"]), + ), + ); + } + if (payload.settleDelivery && action!.deliveryId) { + await notifyChatDeliveryWork(tx); + const [settled] = await tx + .update(chatDeliveries) + .set({ + state: "processed", + processedAt: new Date(), + nextAttemptAt: null, + redactedError: null, + updatedAt: new Date(), + }) + .where( + and( + eq(chatDeliveries.id, action!.deliveryId), + eq(chatDeliveries.state, "processing"), + ), + ) + .returning({ id: chatDeliveries.id }); + if (!settled) { + throw new Error( + "Provider effect delivery ownership changed before commit", + ); + } + } + if (currentEndpoint) { + await tx + .update(chatEndpoints) + .set({ lastEventAt: new Date(), updatedAt: new Date() }) + .where(eq(chatEndpoints.id, currentEndpoint.id)); + } + await credentialLease.assertOwned(tx); + }); + return "processed"; + }, + ); + } catch (error) { + const disposition = providerEffectDisposition( + error, + attempt, + providerEffectCompleted, + ); + try { + await finalizeProviderEffectFailure({ + action, + endpoint: record?.endpoint ?? initialEndpoint.endpoint, + payload, + disposition, + attempt, + }); + } catch (finalizationError) { + if (disposition.kind === "delivery_unknown") { + throw Object.assign( + new Error( + "Provider effect is ambiguous and its durable quarantine failed", + { cause: finalizationError }, + ), + { code: "CHAT_PROVIDER_EFFECT_AMBIGUOUS" }, + ); + } + throw finalizationError; + } + throw error; + } + }; + return processClaimedEffect(); + } + + async function processPendingProviderEffects(limit = 25) { + const now = new Date(); + const staleBefore = new Date(now.getTime() - PROVIDER_EFFECT_STALE_MS); + const actions = await db + .select() + .from(chatActions) + .where(dueChatAction("provider_effect", now.getTime())) + .orderBy(asc(chatActions.createdAt)) + .limit(limit); + for (const action of actions) { + if (action.status === "processing" && action.updatedAt <= staleBefore) { + await quarantineStaleProviderEffect(action); + continue; + } + try { + await processProviderEffect(action.id); + } catch (error) { + logger.warn( + { + endpointId: action.endpointId, + actionId: action.id, + error: redactError(error), + }, + "chat provider effect reconciliation failed", + ); + } + } + return actions.length; + } + return { stageProviderEffect, processProviderEffect, processPendingProviderEffects }; +} diff --git a/server/src/services/chat-channels/reactions.ts b/server/src/services/chat-channels/reactions.ts new file mode 100644 index 00000000000..83df24432e9 --- /dev/null +++ b/server/src/services/chat-channels/reactions.ts @@ -0,0 +1,891 @@ +import type { EndpointRow, DbTransaction, DbOrTransaction, RuntimeContext, LifecycleRuntimeFence, InboundRuntimeContext } from "./runtime-types.js"; + +import { + stableExternalPrincipalId, +} from "./message-lifecycle.js"; + +import { notifyChatDeliveryWork } from "../chat-work-notifications.js"; + +import { + createHash, +} from "node:crypto"; +import { + and, + asc, + desc, + eq, + gt, + gte, + inArray, + isNull, + lt, + lte, + or, + sql, + type SQL, +} from "drizzle-orm"; + +import { + externalThreadIdentityCondition, +} from "../chat-control-chronology.js"; + +import type { Db } from "@paperclipai/db"; + +import { + chatConversations, + chatDeliveries, + chatEndpointResources, + chatMessageLinks, + chatPublications, +} from "@paperclipai/db"; +import type { + ChatEventKind, + ChatProvider, +} from "@paperclipai/shared"; + +import { logger } from "../../middleware/logger.js"; + +import { + type ChatSdkCallbackEvent, +} from "../chat-sdk-runtime.js"; + +import type { + Author, + ReactionEvent, +} from "chat"; + +import type { EndpointRecord } from "./runtime-types.js"; +import { slackThreadChannelId } from "./slack/messages.js"; + +// Reactions can beat the transaction that persists a just-sent provider +// message link. Keep that narrow gap durable, but never retain an unbound +// reaction indefinitely or let it enter the ordinary inbound-message FIFO. +const REACTION_LINK_RETRY_DELAY_MS = 250; +const REACTION_LINK_MAX_DELAY_MS = 5_000; +const REACTION_LINK_MAX_ATTEMPTS = 20; +const REACTION_LINK_MAX_AGE_MS = 2 * 60_000; +type DeliveryRow = typeof chatDeliveries.$inferSelect; +type ConversationRow = typeof chatConversations.$inferSelect; + +type InboundReactionsOptions = { + lockCurrentPrincipalAuthorization: (tx: DbOrTransaction, endpoint: EndpointRow, principalId: string) => Promise<{ allowed: boolean }>; + runtimeCallbackRecord: (endpointId: string, context: RuntimeContext, statuses: EndpointRow["status"][]) => Promise; + runtimeCallbackEndpoint: (tx: DbTransaction, endpointId: string, context: LifecycleRuntimeFence, statuses: EndpointRow["status"][]) => Promise; + ensurePrincipal: (endpoint: EndpointRow, author: Author, raw?: unknown) => Promise<{ principal: { id: string } }>; + durableExternalThreadIdentity: (threadId: string) => string; + recordCurrentMicrosoftTeamsRoute: (endpoint: EndpointRow, context: InboundRuntimeContext | undefined, threadId: string, raw: unknown) => Promise; + lifecycleRuntimeFence: (delivery: DeliveryRow) => LifecycleRuntimeFence | null; + deliveryReady: (delivery: DeliveryRow, now: Date) => boolean; + redactError: (error: unknown) => string; + nonDirectDestinationAllowed: (endpoint: EndpointRow, resource: typeof chatEndpointResources.$inferSelect | null | undefined) => boolean; + reactionLinkPreflightBarrier?: () => Promise; + reactionReplayEndpointLockBarrier?: () => Promise; + reactionReplayConversationLockBarrier?: () => Promise; +}; + +export function createInboundReactions(db: Db, options: InboundReactionsOptions) { + const { lockCurrentPrincipalAuthorization, runtimeCallbackRecord, runtimeCallbackEndpoint, ensurePrincipal, durableExternalThreadIdentity, recordCurrentMicrosoftTeamsRoute, lifecycleRuntimeFence, deliveryReady, redactError, nonDirectDestinationAllowed } = options; + + + function reactionConversationThreadCondition( + provider: ChatProvider, + threadId: string, + ): SQL | undefined { + const exact = externalThreadIdentityCondition( + sql`${chatConversations.externalThreadId}`, + threadId, + ); + const slackDmChannel = + provider === "slack" ? slackThreadChannelId(threadId) : null; + return slackDmChannel && /^D[A-Z0-9-]*$/i.test(slackDmChannel) + ? or( + exact, + and( + eq(chatConversations.isDirectMessage, true), + eq(chatConversations.externalThreadId, `slack:${slackDmChannel}:`), + ), + ) + : exact; + } + + async function reactionConversationForMessage( + database: DbOrTransaction, + endpoint: EndpointRow, + threadId: string, + providerMessageId: string, + direction?: "outbound", + ): Promise { + return database + .select({ conversation: chatConversations }) + .from(chatMessageLinks) + .innerJoin( + chatConversations, + and( + eq(chatConversations.companyId, chatMessageLinks.companyId), + eq(chatConversations.endpointId, chatMessageLinks.endpointId), + eq(chatConversations.id, chatMessageLinks.conversationId), + ), + ) + .where( + and( + eq(chatMessageLinks.companyId, endpoint.companyId), + eq(chatMessageLinks.endpointId, endpoint.id), + eq(chatMessageLinks.providerMessageId, providerMessageId), + direction ? eq(chatMessageLinks.direction, direction) : undefined, + reactionConversationThreadCondition(endpoint.provider, threadId), + ), + ) + .orderBy(desc(chatConversations.sessionGeneration)) + .limit(1) + .then((rows) => rows[0]?.conversation ?? null); + } + + async function lockReactionConversationForMessage( + tx: DbTransaction, + endpoint: EndpointRow, + threadId: string, + providerMessageId: string, + direction?: "outbound", + ): Promise { + const candidate = await reactionConversationForMessage( + tx, + endpoint, + threadId, + providerMessageId, + direction, + ); + if (!candidate) return null; + const current = await tx + .select() + .from(chatConversations) + .where( + and( + eq(chatConversations.companyId, endpoint.companyId), + eq(chatConversations.endpointId, endpoint.id), + eq(chatConversations.id, candidate.id), + ), + ) + // Publication settlement inserts the outbound message link before it + // updates the endpoint. NO KEY UPDATE serializes state changes while + // remaining compatible with that link's conversation FK KEY SHARE. + .for("no key update") + .then((rows) => rows[0] ?? null); + if ( + !current || + !(current.isDirectMessage + ? ["active", "waiting", "completed"].includes(current.state) + : ["active", "waiting"].includes(current.state)) + ) { + return null; + } + const linkStillExists = await tx + .select({ id: chatMessageLinks.id }) + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.companyId, endpoint.companyId), + eq(chatMessageLinks.endpointId, endpoint.id), + eq(chatMessageLinks.conversationId, current.id), + eq(chatMessageLinks.providerMessageId, providerMessageId), + direction ? eq(chatMessageLinks.direction, direction) : undefined, + ), + ) + .limit(1) + .then((rows) => rows.length > 0); + return linkStillExists ? current : null; + } + + async function streamingReactionConversation( + database: DbOrTransaction, + endpoint: EndpointRow, + threadId: string, + ): Promise { + const candidates = await database + .selectDistinct({ + conversation: chatConversations, + sessionGeneration: chatConversations.sessionGeneration, + }) + .from(chatConversations) + .innerJoin( + chatPublications, + and( + eq(chatPublications.companyId, chatConversations.companyId), + eq(chatPublications.endpointId, chatConversations.endpointId), + eq(chatPublications.conversationId, chatConversations.id), + ), + ) + .where( + and( + eq(chatConversations.companyId, endpoint.companyId), + eq(chatConversations.endpointId, endpoint.id), + or( + inArray(chatConversations.state, ["active", "waiting"]), + and( + eq(chatConversations.isDirectMessage, true), + eq(chatConversations.state, "completed"), + ), + ), + reactionConversationThreadCondition(endpoint.provider, threadId), + eq(chatPublications.state, "streaming"), + ), + ) + .orderBy(desc(chatConversations.sessionGeneration)) + .limit(2); + if (candidates.length !== 1) return null; + return candidates[0]!.conversation; + } + + async function lockStreamingReactionConversation( + tx: DbTransaction, + endpoint: EndpointRow, + threadId: string, + ): Promise { + const candidate = await streamingReactionConversation( + tx, + endpoint, + threadId, + ); + if (!candidate) return null; + const current = await tx + .select() + .from(chatConversations) + .where( + and( + eq(chatConversations.companyId, endpoint.companyId), + eq(chatConversations.endpointId, endpoint.id), + eq(chatConversations.id, candidate.id), + or( + inArray(chatConversations.state, ["active", "waiting"]), + and( + eq(chatConversations.isDirectMessage, true), + eq(chatConversations.state, "completed"), + ), + ), + ), + ) + // Keep the same lock strength/order as the exact-link path above so an + // in-flight message-link insert cannot deadlock reaction admission. + .for("no key update") + .then((rows) => rows[0] ?? null); + if (!current) return null; + const stillStreaming = await tx + .select({ id: chatPublications.id }) + .from(chatPublications) + .where( + and( + eq(chatPublications.companyId, endpoint.companyId), + eq(chatPublications.endpointId, endpoint.id), + eq(chatPublications.conversationId, current.id), + eq(chatPublications.state, "streaming"), + ), + ) + .limit(1) + .then((rows) => rows.length > 0); + return stillStreaming ? current : null; + } + + async function authorizeReactionConversation( + tx: DbTransaction, + endpoint: EndpointRow, + conversation: ConversationRow, + principalId: string, + ): Promise { + const resource = + conversation.resourceId && !conversation.isDirectMessage + ? await tx + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.companyId, endpoint.companyId), + eq(chatEndpointResources.endpointId, endpoint.id), + eq(chatEndpointResources.id, conversation.resourceId), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null) + : null; + const destinationAllowed = conversation.isDirectMessage + ? endpoint.allowDirectMessages + : nonDirectDestinationAllowed(endpoint, resource); + if (!destinationAllowed) return false; + return (await lockCurrentPrincipalAuthorization(tx, endpoint, principalId)) + .allowed; + } + + async function handleReaction( + event: ChatSdkCallbackEvent, + runtimeContext: RuntimeContext, + ) { + const record = await runtimeCallbackRecord( + event.endpointId, + runtimeContext, + ["active"], + ); + if ( + !record || + record.endpoint.status !== "active" || + record.endpoint.provider !== event.provider || + record.endpoint.capabilities.reactions !== true || + event.event.user.isMe || + event.event.user.isBot === true || + event.event.user.isSystem + ) { + return; + } + // A DM can have a newer task generation while this reaction still belongs + // to an older linked message. Resolve its exact lineage before choosing a + // generation; never attach it to the latest task merely sharing the chat. + // A just-sent provider message can be visible before its publication/link + // transaction commits. Only that exact thread's streaming publication is + // enough evidence to stage an otherwise-unbound reaction for replay. + let conversation = await reactionConversationForMessage( + db, + record.endpoint, + event.event.threadId, + event.event.messageId, + ); + await options.reactionLinkPreflightBarrier?.(); + const plausibleStreamingConversation = conversation + ? null + : await streamingReactionConversation( + db, + record.endpoint, + event.event.threadId, + ); + if (!conversation && !plausibleStreamingConversation) { + // Settlement can commit between the first exact-link lookup and the + // streaming-publication lookup. Recheck the durable link before treating + // the callback as an arbitrary unknown message. + conversation = await reactionConversationForMessage( + db, + record.endpoint, + event.event.threadId, + event.event.messageId, + ); + if (!conversation) return; + } + + const principal = await ensurePrincipal( + record.endpoint, + event.event.user, + event.event.raw, + ); + let rawFingerprint: string; + try { + rawFingerprint = createHash("sha256") + .update(JSON.stringify(event.event.raw ?? null)) + .digest("hex") + .slice(0, 24); + } catch { + rawFingerprint = "unavailable"; + } + const eventKind: ChatEventKind = event.event.added + ? "reaction_added" + : "reaction_removed"; + const providerEventId = [ + eventKind, + durableExternalThreadIdentity(event.event.threadId), + event.event.messageId, + stableExternalPrincipalId( + record.endpoint.provider, + event.event.user, + event.event.raw, + ), + event.event.rawEmoji, + rawFingerprint, + ].join(":"); + const normalizedEvent = { + providerEventId, + kind: eventKind, + conversation: { externalThreadId: event.event.threadId }, + message: { providerMessageId: event.event.messageId }, + reaction: { + emoji: event.event.emoji.name, + rawEmoji: event.event.rawEmoji, + added: event.event.added, + }, + runtimeContext: { + generation: runtimeContext.generation, + credentialFingerprint: runtimeContext.credentialFingerprint, + }, + }; + const admitted = await db.transaction(async (tx) => { + const currentEndpoint = await runtimeCallbackEndpoint( + tx, + event.endpointId, + runtimeContext, + ["active"], + ); + if (!currentEndpoint) return false; + const linkedConversation = await lockReactionConversationForMessage( + tx, + currentEndpoint, + event.event.threadId, + event.event.messageId, + ); + const currentConversation = + linkedConversation ?? + (await lockStreamingReactionConversation( + tx, + currentEndpoint, + event.event.threadId, + )); + if ( + !currentConversation || + !(await authorizeReactionConversation( + tx, + currentEndpoint, + currentConversation, + principal.principal.id, + )) + ) { + return false; + } + const now = new Date(); + if (linkedConversation) { + const expiryCutoff = new Date(now.getTime() - REACTION_LINK_MAX_AGE_MS); + const hasOutboundLink = await tx + .select({ id: chatMessageLinks.id }) + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.companyId, currentEndpoint.companyId), + eq(chatMessageLinks.endpointId, currentEndpoint.id), + eq(chatMessageLinks.conversationId, linkedConversation.id), + eq(chatMessageLinks.providerMessageId, event.event.messageId), + eq(chatMessageLinks.direction, "outbound"), + ), + ) + .limit(1) + .then((rows) => rows.length > 0); + await notifyChatDeliveryWork(tx); + const reconciled = hasOutboundLink + ? await tx + .update(chatDeliveries) + .set({ + conversationId: linkedConversation.id, + state: "processed", + attempts: sql`${chatDeliveries.attempts} + 1`, + nextAttemptAt: null, + processedAt: now, + redactedError: null, + updatedAt: now, + }) + .where( + and( + eq(chatDeliveries.endpointId, currentEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + inArray(chatDeliveries.state, ["received", "retry"]), + gt(chatDeliveries.receivedAt, expiryCutoff), + lt(chatDeliveries.attempts, REACTION_LINK_MAX_ATTEMPTS), + sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'generation' = ${String(runtimeContext.generation)}`, + sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'credentialFingerprint' = ${runtimeContext.credentialFingerprint}`, + ), + ) + .returning({ id: chatDeliveries.id }) + : []; + if (reconciled.length > 0) return true; + await notifyChatDeliveryWork(tx); + const expired = await tx + .update(chatDeliveries) + .set({ + state: "filtered", + nextAttemptAt: null, + processedAt: now, + redactedError: + "Reaction target did not become linked before replay expiry", + updatedAt: now, + }) + .where( + and( + eq(chatDeliveries.endpointId, currentEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + inArray(chatDeliveries.state, ["received", "retry"]), + or( + lte(chatDeliveries.receivedAt, expiryCutoff), + gte(chatDeliveries.attempts, REACTION_LINK_MAX_ATTEMPTS), + ), + sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'generation' = ${String(runtimeContext.generation)}`, + sql`${chatDeliveries.normalizedEvent}->'runtimeContext'->>'credentialFingerprint' = ${runtimeContext.credentialFingerprint}`, + ), + ) + .returning({ id: chatDeliveries.id }); + if (expired.length > 0) return false; + } + await notifyChatDeliveryWork(tx); + const inserted = await tx + .insert(chatDeliveries) + .values({ + companyId: currentEndpoint.companyId, + endpointId: currentEndpoint.id, + conversationId: linkedConversation?.id ?? null, + principalId: principal.principal.id, + providerEventId, + deduplicationKey: createHash("sha256") + .update(providerEventId) + .digest("hex"), + eventKind, + normalizedEvent, + state: linkedConversation ? "processed" : "received", + attempts: linkedConversation ? 1 : 0, + nextAttemptAt: linkedConversation + ? null + : new Date(now.getTime() + REACTION_LINK_RETRY_DELAY_MS), + processedAt: linkedConversation ? now : null, + }) + .onConflictDoNothing() + .returning({ id: chatDeliveries.id }); + if (inserted.length > 0) return true; + return tx + .select({ id: chatDeliveries.id }) + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.endpointId, currentEndpoint.id), + eq(chatDeliveries.providerEventId, providerEventId), + inArray(chatDeliveries.state, ["received", "retry", "processed"]), + ), + ) + .limit(1) + .then((rows) => rows.length > 0); + }); + if (admitted) { + await recordCurrentMicrosoftTeamsRoute( + record.endpoint, + runtimeContext, + event.event.threadId, + event.event.raw, + ); + } + } + + function normalizedPendingReaction(delivery: DeliveryRow): { + added: boolean; + emoji: string; + messageId: string; + rawEmoji: string; + runtimeContext: LifecycleRuntimeFence; + threadId: string; + } | null { + if ( + delivery.eventKind !== "reaction_added" && + delivery.eventKind !== "reaction_removed" + ) { + return null; + } + const normalized = delivery.normalizedEvent; + if ( + !normalized || + typeof normalized !== "object" || + Array.isArray(normalized) + ) { + return null; + } + const value = normalized as Record; + const conversation = value.conversation; + const message = value.message; + const reaction = value.reaction; + const runtimeContext = lifecycleRuntimeFence(delivery); + if ( + !conversation || + typeof conversation !== "object" || + Array.isArray(conversation) || + !message || + typeof message !== "object" || + Array.isArray(message) || + !reaction || + typeof reaction !== "object" || + Array.isArray(reaction) || + !runtimeContext + ) { + return null; + } + const threadId = (conversation as Record).externalThreadId; + const messageId = (message as Record).providerMessageId; + const reactionRecord = reaction as Record; + const added = reactionRecord.added; + const emoji = reactionRecord.emoji; + const rawEmoji = reactionRecord.rawEmoji; + return typeof threadId === "string" && + threadId.length > 0 && + typeof messageId === "string" && + messageId.length > 0 && + typeof added === "boolean" && + added === (delivery.eventKind === "reaction_added") && + typeof emoji === "string" && + emoji.length > 0 && + typeof rawEmoji === "string" && + rawEmoji.length > 0 + ? { added, emoji, messageId, rawEmoji, runtimeContext, threadId } + : null; + } + + async function processPendingReactionDelivery( + candidate: DeliveryRow, + ): Promise { + const reaction = normalizedPendingReaction(candidate); + if (!reaction || !candidate.principalId) { + const now = new Date(); + await db.transaction(async (tx) => { + await notifyChatDeliveryWork(tx); + return tx + .update(chatDeliveries) + .set({ + state: "failed", + nextAttemptAt: null, + processedAt: now, + redactedError: "Normalized reaction delivery is incomplete", + updatedAt: now, + }) + .where( + and( + eq(chatDeliveries.id, candidate.id), + inArray(chatDeliveries.state, ["received", "retry"]), + ), + ); + }); + return; + } + const principalId = candidate.principalId; + + await db.transaction(async (tx) => { + const currentEndpoint = await runtimeCallbackEndpoint( + tx, + candidate.endpointId, + reaction.runtimeContext, + ["active"], + ); + let conversation: ConversationRow | null = null; + let disposition: + | { kind: "processed"; conversationId: string } + | { kind: "retry" } + | { kind: "filtered"; reason: string }; + + if (!currentEndpoint) { + disposition = { + kind: "filtered", + reason: "Reaction callback belonged to an unavailable runtime", + }; + } else { + await options.reactionReplayEndpointLockBarrier?.(); + conversation = await lockReactionConversationForMessage( + tx, + currentEndpoint, + reaction.threadId, + reaction.messageId, + "outbound", + ); + if (conversation) { + await options.reactionReplayConversationLockBarrier?.(); + } + if (conversation) { + disposition = (await authorizeReactionConversation( + tx, + currentEndpoint, + conversation, + principalId, + )) + ? { kind: "processed", conversationId: conversation.id } + : { + kind: "filtered", + reason: + "Reaction destination or principal is no longer authorized", + }; + } else { + conversation = await lockStreamingReactionConversation( + tx, + currentEndpoint, + reaction.threadId, + ); + if (conversation) { + await options.reactionReplayConversationLockBarrier?.(); + } + if (!conversation) { + disposition = { + kind: "filtered", + reason: "Reaction target was not linked to a sent message", + }; + } else { + disposition = (await authorizeReactionConversation( + tx, + currentEndpoint, + conversation, + principalId, + )) + ? { kind: "retry" } + : { + kind: "filtered", + reason: + "Reaction destination or principal is no longer authorized", + }; + } + } + } + + // Endpoint, conversation, destination and identity locks are acquired + // before the delivery row so pause/revoke and replay use one lock order. + const locked = await tx + .select() + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.id, candidate.id), + inArray(chatDeliveries.state, ["received", "retry"]), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + // Authorization locks may wait behind a concurrent lifecycle mutation. + // Sample the expiry clock only after this delivery is exclusively held. + const decisionAt = new Date(); + if (!locked || !deliveryReady(locked, decisionAt)) return; + const lockedReaction = normalizedPendingReaction(locked); + if ( + !lockedReaction || + lockedReaction.runtimeContext.generation !== + reaction.runtimeContext.generation || + lockedReaction.runtimeContext.credentialFingerprint !== + reaction.runtimeContext.credentialFingerprint + ) { + disposition = { + kind: "filtered", + reason: "Reaction delivery identity changed before replay", + }; + } + + const nextAttempts = locked.attempts + 1; + const expired = + decisionAt.getTime() - locked.receivedAt.getTime() >= + REACTION_LINK_MAX_AGE_MS; + if (expired || locked.attempts >= REACTION_LINK_MAX_ATTEMPTS) { + disposition = { + kind: "filtered", + reason: "Reaction target did not become linked before replay expiry", + }; + } + if (disposition.kind === "retry" && !expired) { + if (nextAttempts < REACTION_LINK_MAX_ATTEMPTS) { + const delay = Math.min( + REACTION_LINK_RETRY_DELAY_MS * + 2 ** Math.min(Math.max(locked.attempts, 0), 5), + REACTION_LINK_MAX_DELAY_MS, + ); + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set({ + state: "retry", + attempts: nextAttempts, + nextAttemptAt: new Date(decisionAt.getTime() + delay), + redactedError: "Waiting for the sent message link", + updatedAt: decisionAt, + }) + .where( + and( + eq(chatDeliveries.id, locked.id), + eq(chatDeliveries.state, locked.state), + eq(chatDeliveries.attempts, locked.attempts), + ), + ); + return; + } + disposition = { + kind: "filtered", + reason: "Reaction target did not become linked before retry expiry", + }; + } else if (disposition.kind === "retry") { + disposition = { + kind: "filtered", + reason: "Reaction target did not become linked before age expiry", + }; + } + + const processedAt = new Date(); + await notifyChatDeliveryWork(tx); + await tx + .update(chatDeliveries) + .set( + disposition.kind === "processed" + ? { + conversationId: disposition.conversationId, + state: "processed", + attempts: nextAttempts, + nextAttemptAt: null, + processedAt, + redactedError: null, + updatedAt: processedAt, + } + : { + state: "filtered", + attempts: nextAttempts, + nextAttemptAt: null, + processedAt, + redactedError: disposition.reason, + updatedAt: processedAt, + }, + ) + .where( + and( + eq(chatDeliveries.id, locked.id), + eq(chatDeliveries.state, locked.state), + eq(chatDeliveries.attempts, locked.attempts), + ), + ); + }); + } + + async function processPendingReactionDeliveries( + limit: number, + onlyDeliveryId?: string, + ): Promise { + const now = new Date(); + const candidates = await db + .select() + .from(chatDeliveries) + .where( + and( + sql`not exists (select 1 from chat_endpoints e where e.id = ${chatDeliveries.endpointId} and e.provider = 'agentmail')`, + onlyDeliveryId ? eq(chatDeliveries.id, onlyDeliveryId) : undefined, + inArray(chatDeliveries.eventKind, [ + "reaction_added", + "reaction_removed", + ]), + or( + and( + eq(chatDeliveries.state, "received"), + or( + isNull(chatDeliveries.nextAttemptAt), + lte(chatDeliveries.nextAttemptAt, now), + ), + ), + and( + eq(chatDeliveries.state, "retry"), + or( + isNull(chatDeliveries.nextAttemptAt), + lte(chatDeliveries.nextAttemptAt, now), + ), + ), + ), + ), + ) + .orderBy(asc(chatDeliveries.receivedAt), asc(chatDeliveries.id)) + .limit(limit); + for (const candidate of candidates) { + try { + await processPendingReactionDelivery(candidate); + } catch (error) { + logger.warn( + { + endpointId: candidate.endpointId, + deliveryId: candidate.id, + error: redactError(error), + }, + "chat reaction-link reconciliation failed", + ); + } + } + return candidates.length; + } + return { handleReaction, processPendingReactionDeliveries }; +} diff --git a/server/src/services/chat-channels/receipt-reactions.ts b/server/src/services/chat-channels/receipt-reactions.ts new file mode 100644 index 00000000000..a733091a127 --- /dev/null +++ b/server/src/services/chat-channels/receipt-reactions.ts @@ -0,0 +1,586 @@ +import type { ChatRuntime } from "../chat-runtime.js"; +import type { EndpointRow, RuntimeContext, LifecycleRuntimeFence } from "./runtime-types.js"; + +import { dueChatAction, PROVIDER_EFFECT_STALE_MS } from "../chat-action-work.js"; + +import { notifyChatActionWork } from "../chat-work-notifications.js"; + +import type { ChatEndpointRuntime } from "../voice/voice-runtime.js"; + +import { + receiptReactionPayload, + type ReceiptReactionPayload, +} from "../chat-receipt-reactions.js"; + +import { chatCredentialMutationLease, type CredentialMutationLeaseGuard } from "../chat-credential-mutation-lease.js"; + +import { + and, + asc, + eq, + like, + sql, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + chatActions, + chatDeliveries, + chatEndpoints, + toolConnections, +} from "@paperclipai/db"; +import type { + ChatEndpointSetupState, + ChatProvider, + ToolCredentialSecretRef, +} from "@paperclipai/shared"; + +import { + parseGitHubReceiptIdentity, + type GitHubReceiptIdentity, +} from "../chat-github-receipt-reactions.js"; + +import { logger } from "../../middleware/logger.js"; + +import { classifyChatPublicationError } from "../chat-publication-errors.js"; + +import type { + Message, + Thread, +} from "chat"; + +import type { EndpointRecord } from "./runtime-types.js"; +import { isSlackReceiptReactionAlreadyApplied } from "./slack/messages.js"; + +type ReceiptReactionsOptions = { + endpointRecord: (endpointId: string) => Promise; + runtimeContextForRecord: (record: EndpointRecord) => RuntimeContext; + withCredentialMutationLease: ReturnType; + runtimeFor: (endpoint: EndpointRow) => Promise; + fetchImpl: typeof fetch; + runtime: Pick; + runtimeGeneration: (setup: ChatEndpointSetupState) => number; + credentialFingerprint: (refs: ToolCredentialSecretRef[]) => string; + redactError: (error: unknown) => string; + MAX_ERROR_TEXT: number; + receiptReactionTransportBarrier?: () => Promise; +}; + +export function createReceiptReactions(db: Db, options: ReceiptReactionsOptions) { + const { endpointRecord, runtimeContextForRecord, withCredentialMutationLease, runtimeFor, fetchImpl, runtime, runtimeGeneration, credentialFingerprint, redactError, MAX_ERROR_TEXT } = options; + + + async function addReceiptReaction(input: { + deliveryId: string; + endpoint: EndpointRow; + message: Message; + runtimeContext?: LifecycleRuntimeFence; + thread: Thread; + }): Promise { + // GitHub acknowledges through one editable response comment. Continue to + // drain legacy eyes removals, but never create new eyes for these requests. + if (input.endpoint.provider === "github") return; + const record = await endpointRecord(input.endpoint.id); + if (!record) return; + // A live SDK object remains bound to the credentials that authenticated + // its callback. Preserve that original fence rather than sampling the + // endpoint again after a rotation and accidentally blessing an obsolete + // adapter with the new generation. + const context = input.runtimeContext ?? runtimeContextForRecord(record); + const [inserted] = await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "receipt_reaction"); + return tx + .insert(chatActions) + .values({ + companyId: input.endpoint.companyId, + endpointId: input.endpoint.id, + deliveryId: input.deliveryId, + kind: "receipt_reaction", + providerActionId: `receipt_reaction:${input.deliveryId}`, + payload: { + version: 1, + operation: "add", + threadId: input.thread.id, + messageId: input.message.id, + reaction: "eyes", + runtimeGeneration: context.generation, + credentialFingerprint: context.credentialFingerprint, + } satisfies ReceiptReactionPayload, + status: "received", + }) + .onConflictDoNothing() + .returning({ id: chatActions.id }); + }); + const actionId = + inserted?.id ?? + (await db + .select({ id: chatActions.id }) + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, input.endpoint.id), + eq( + chatActions.providerActionId, + `receipt_reaction:${input.deliveryId}`, + ), + ), + ) + .then((rows) => rows[0]?.id ?? null)); + if (!actionId) return; + await options.receiptReactionTransportBarrier?.(); + await processReceiptReaction(actionId); + } + + async function processReceiptReaction(actionId: string): Promise { + const initialAction = await db + .select({ endpointId: chatActions.endpointId }) + .from(chatActions) + .where( + and( + eq(chatActions.id, actionId), + eq(chatActions.kind, "receipt_reaction"), + ), + ) + .then((rows) => rows[0] ?? null); + if (!initialAction) return; + const initialEndpoint = await endpointRecord(initialAction.endpointId); + if (!initialEndpoint) return; + // Receipt reactions are GitHub repository mutations too. Serialize their + // final runtime-fence check and provider call with reconnect, secret + // rotation, pause, and removal so credentials cannot become obsolete + // between authorization and transport. + await withCredentialMutationLease(initialEndpoint.endpoint, (lease) => + processReceiptReactionWithCredentialLease(actionId, lease), + ); + } + + async function processReceiptReactionWithCredentialLease( + actionId: string, + credentialLease: CredentialMutationLeaseGuard, + ): Promise { + let action = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.id, actionId), + eq(chatActions.kind, "receipt_reaction"), + ), + ) + .then((rows) => rows[0] ?? null); + if (!action || ["processed", "cancelled"].includes(action.status)) return; + const payload = receiptReactionPayload(action.payload); + if (!payload) { + await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "receipt_reaction"); + return tx + .update(chatActions) + .set({ + status: "failed", + result: { + code: "receipt_reaction_payload_invalid", + retryable: false, + }, + updatedAt: new Date(), + }) + .where(eq(chatActions.id, action.id)); + }); + return; + } + if (payload.operation === "add" && action.deliveryId) { + const terminalRemoval = await db + .select({ id: chatActions.id }) + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, action.endpointId), + eq( + chatActions.providerActionId, + `receipt_reaction_remove:${action.deliveryId}`, + ), + ), + ) + .then((rows) => rows[0] ?? null); + if (terminalRemoval) { + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + await tx + .update(chatActions) + .set({ + status: "cancelled", + result: { + ...(typeof action!.result?.attempts === "number" + ? { attempts: action!.result.attempts } + : {}), + code: "receipt_reaction_superseded_by_terminal_publication", + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.status, action!.status), + ), + ); + await credentialLease.assertOwned(tx); + }); + return; + } + } + if (action.status === "processing") { + if (action.updatedAt > new Date(Date.now() - PROVIDER_EFFECT_STALE_MS)) + return; + // Adding or removing the same reaction is idempotent. A worker + // interruption can be retried safely without an ambiguous + // duplicate-message boundary. + await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "receipt_reaction"); + return tx + .update(chatActions) + .set({ + status: "failed", + result: { + ...(action.result ?? {}), + retryable: true, + retryAt: new Date().toISOString(), + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "processing"), + eq(chatActions.updatedAt, action.updatedAt), + ), + ); + }); + action = await db + .select() + .from(chatActions) + .where(eq(chatActions.id, action.id)) + .then((rows) => rows[0] ?? null); + if (!action) return; + } + if (action.status === "failed") { + if (action.result?.retryable !== true) return; + const retryAt = + typeof action.result.retryAt === "string" + ? new Date(action.result.retryAt) + : null; + if (retryAt && retryAt > new Date()) return; + } else if (action.status !== "received") { + return; + } + const attempt = + (typeof action.result?.attempts === "number" ? action.result.attempts : 0) + + 1; + const retainedGitHubReceipt = parseGitHubReceiptIdentity( + action.result?.githubReceipt, + ); + const [claimed] = await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + const rows = await tx + .update(chatActions) + .set({ + status: "processing", + result: { + attempts: attempt, + ...(retainedGitHubReceipt + ? { githubReceipt: retainedGitHubReceipt } + : {}), + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.status, action!.status), + ), + ) + .returning(); + await credentialLease.assertOwned(tx); + return rows; + }); + if (!claimed) return; + + let reactionProvider: ChatProvider | null = null; + let githubReceipt: GitHubReceiptIdentity | null = retainedGitHubReceipt; + try { + const record = await endpointRecord(action.endpointId); + if ( + !record || + !["verifying", "active"].includes(record.endpoint.status) || + runtimeGeneration(record.endpoint.setup) !== payload.runtimeGeneration || + credentialFingerprint(record.credentialSecretRefs) !== + payload.credentialFingerprint + ) { + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + await tx + .update(chatActions) + .set({ + status: "cancelled", + result: { + attempts: attempt, + code: "receipt_reaction_runtime_superseded", + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ); + await credentialLease.assertOwned(tx); + }); + return; + } + reactionProvider = record.endpoint.provider; + // Resolve the adapter only after acquiring the credential lease and + // proving the action's durable generation/fingerprint is still current. + // Never reuse the live callback's adapter: it may hold an installation + // token minted by a superseded private key. + const endpointRuntime = await runtimeFor(record.endpoint); + const adapter = endpointRuntime.thread(payload.threadId).adapter; + await credentialLease.assertOwned(); + if (record.endpoint.provider === "slack") { + await endpointRuntime.applySlackReceiptReaction(payload, fetchImpl); + } else if (record.endpoint.provider === "github") { + const assertCurrent = async () => { + await credentialLease.assertOwned(); + const [current] = await db + .select({ endpoint: chatEndpoints, connection: toolConnections }) + .from(chatEndpoints) + .innerJoin( + toolConnections, + and( + eq(toolConnections.id, chatEndpoints.connectionId), + eq(toolConnections.companyId, chatEndpoints.companyId), + ), + ) + .where( + and( + eq(chatEndpoints.id, action!.endpointId), + eq(chatEndpoints.companyId, action!.companyId), + ), + ) + .limit(1); + if ( + !current || + !["active", "verifying"].includes(current.endpoint.status) || + !current.connection.enabled || + current.connection.status !== "active" || + runtimeGeneration(current.endpoint.setup) !== + payload.runtimeGeneration || + credentialFingerprint(current.connection.credentialSecretRefs) !== + payload.credentialFingerprint || + runtime.get(action!.endpointId) !== endpointRuntime + ) + throw new Error("GitHub receipt authority is no longer current"); + }; + const applied = await endpointRuntime.applyGitHubReceiptReaction( + { ...payload, ...(githubReceipt ? { githubReceipt } : {}) }, + assertCurrent, + fetchImpl, + ); + githubReceipt = parseGitHubReceiptIdentity(applied); + if ( + !githubReceipt || + (payload.operation === "add" && !githubReceipt.reactionId) + ) + throw new Error("GitHub receipt identity was not confirmed"); + } else if (payload.operation === "remove") { + await adapter.removeReaction( + payload.threadId, + payload.messageId, + payload.reaction, + ); + } else { + await adapter.addReaction( + payload.threadId, + payload.messageId, + payload.reaction, + ); + } + // The provider may already have accepted the idempotent reaction. Fence + // the local success receipt so a reclaimed lease cannot report success + // for an obsolete runtime. + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + const processed = await tx + .update(chatActions) + .set({ + status: "processed", + result: { + attempts: attempt, + ...(githubReceipt ? { githubReceipt } : {}), + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (!processed.length) { + throw new Error( + "Receipt-reaction ownership changed before success settlement", + ); + } + if (action.deliveryId) { + await tx + .update(chatDeliveries) + .set({ redactedError: null, updatedAt: new Date() }) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.state, "processed"), + like(chatDeliveries.redactedError, "Receipt reaction %"), + ), + ); + } + await credentialLease.assertOwned(tx); + }); + } catch (error) { + if ( + payload.operation === "add" && + reactionProvider === "slack" && + isSlackReceiptReactionAlreadyApplied(error) + ) { + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + const processed = await tx + .update(chatActions) + .set({ + status: "processed", + result: { + attempts: attempt, + code: "receipt_reaction_already_applied", + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action!.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (!processed.length) { + throw new Error( + "Receipt-reaction ownership changed before idempotent settlement", + ); + } + if (action.deliveryId) { + await tx + .update(chatDeliveries) + .set({ redactedError: null, updatedAt: new Date() }) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.state, "processed"), + like(chatDeliveries.redactedError, "Receipt reaction %"), + ), + ); + } + await credentialLease.assertOwned(tx); + }); + return; + } + const disposition = classifyChatPublicationError(error, attempt); + const retryable = + disposition.kind === "retry" || + (disposition.kind === "delivery_unknown" && attempt < 3); + const retryAfterMs = + disposition.kind === "retry" + ? disposition.retryAfterMs + : 100 * 2 ** Math.max(0, attempt - 1); + const operationLabel = + payload.operation === "remove" ? " reaction removal" : " reaction"; + const diagnostic = + `Receipt${operationLabel} ${retryable ? "deferred" : "failed"} after ${attempt} attempt${attempt === 1 ? "" : "s"} (${disposition.kind}): ${redactError(error)}`.slice( + 0, + MAX_ERROR_TEXT, + ); + const retryAt = retryable + ? new Date(Date.now() + retryAfterMs).toISOString() + : null; + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + await notifyChatActionWork(tx, "receipt_reaction"); + const [failed] = await tx + .update(chatActions) + .set({ + status: "failed", + result: { + attempts: attempt, + ...(githubReceipt ? { githubReceipt } : {}), + code: + payload.operation === "remove" + ? `receipt_reaction_removal_${disposition.kind}` + : `receipt_reaction_${disposition.kind}`, + redactedError: diagnostic, + retryable, + retryAt, + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "processing"), + sql`(${chatActions.result}->>'attempts')::int = ${attempt}`, + ), + ) + .returning({ id: chatActions.id }); + if (failed && action.deliveryId) { + await tx + .update(chatDeliveries) + .set({ redactedError: diagnostic, updatedAt: new Date() }) + .where( + and( + eq(chatDeliveries.id, action.deliveryId), + eq(chatDeliveries.state, "processed"), + ), + ); + } + await credentialLease.assertOwned(tx); + }); + logger.warn( + { + endpointId: action.endpointId, + deliveryId: action.deliveryId, + attempts: attempt, + disposition: disposition.kind, + operation: payload.operation, + retryAt, + }, + "external chat receipt reaction deferred", + ); + } + } + + async function processPendingReceiptReactions( + limit = 25, + onlyActionId?: string, + ) { + const actions = await db.select({ id: chatActions.id }).from(chatActions) + .where(and(dueChatAction("receipt_reaction"), onlyActionId ? eq(chatActions.id, onlyActionId) : undefined)) + .orderBy(asc(chatActions.createdAt)).limit(limit); + for (const action of actions) await processReceiptReaction(action.id); + return actions.length; + } + return { addReceiptReaction, processReceiptReaction, processPendingReceiptReactions }; +} diff --git a/server/src/services/chat-channels/runtime-types.ts b/server/src/services/chat-channels/runtime-types.ts index a764a0297eb..cd2f6881e9b 100644 --- a/server/src/services/chat-channels/runtime-types.ts +++ b/server/src/services/chat-channels/runtime-types.ts @@ -1,5 +1,5 @@ import type { Db, chatEndpoints } from "@paperclipai/db"; -import type { ToolCredentialSecretRef } from "@paperclipai/shared"; +import type { ToolCredentialSecretRef, ChatEndpointSetupState, ChatEndpointCallbackSurfaces } from "@paperclipai/shared"; import type { ChatEndpointRuntime } from "../voice/voice-runtime.js"; export type EndpointRow = typeof chatEndpoints.$inferSelect; @@ -36,3 +36,17 @@ export type EndpointRecord = { applicationId: string; credentialSecretRefs: ToolCredentialSecretRef[]; }; + +export type SlackCallbackSurface = keyof ChatEndpointCallbackSurfaces; + +export type SlackCallbackObservation = { url: string; observedAt: string }; + +export type InternalSetupState = ChatEndpointSetupState & { + photonIntakeAfter?: string; + runtimeGeneration?: number; + /** Binds pre-install URL verification to the signing secret later configured. */ + slackVerificationSigningFingerprint?: string; + slackCallbackSurfaces?: Partial< + Record + >; +}; diff --git a/server/src/services/chat-channels/setup-probes.ts b/server/src/services/chat-channels/setup-probes.ts new file mode 100644 index 00000000000..36d0f8f9f66 --- /dev/null +++ b/server/src/services/chat-channels/setup-probes.ts @@ -0,0 +1,353 @@ +import type { EndpointRow, DbOrTransaction } from "./runtime-types.js"; + +import { notifyChatEndpointWork } from "../chat-work-notifications.js"; + +import { chatCredentialMutationLease } from "../chat-credential-mutation-lease.js"; + +import { githubChatManagementService } from "../chat-github-management.js"; + +import { + and, + asc, + desc, + eq, + gte, + inArray, + isNotNull, + or, + sql, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + chatActions, + chatConversations, + chatDeliveries, + chatEndpoints, + chatIdentityLinks, + chatMessageLinks, + chatPublications, + heartbeatRuns, + issueComments, + toolConnections, +} from "@paperclipai/db"; +import type { + ChatEndpoint, + ChatEndpointSetupState, +} from "@paperclipai/shared"; + +import { + conflict, + forbidden, + notFound, +} from "../../errors.js"; + +import { + logActivity, +} from "../activity-log.js"; + +import { + githubRunReplyState, +} from "../chat-run-publications.js"; + +import type { EndpointRecord } from "./runtime-types.js"; + +type SetupProbesOptions = { + endpointRecord: (endpointId: string) => Promise; + withCredentialMutationLease: ReturnType; + fetchImpl: typeof fetch; + findAuthorizedIdentityLink: (endpoint: EndpointRow, userId: string) => Promise<{ id: string } | null>; + runtimeGeneration: (setup: ChatEndpointSetupState) => number; + lockCurrentPrincipalAuthorization: (tx: DbOrTransaction, endpoint: EndpointRow, principalId: string) => Promise<{ allowed: boolean }>; + get: (endpointId: string) => Promise; + setupTestActivationBarrier?: () => Promise; +}; + +export function createSetupProbes(db: Db, options: SetupProbesOptions) { + const { endpointRecord, withCredentialMutationLease, fetchImpl, findAuthorizedIdentityLink, runtimeGeneration, lockCurrentPrincipalAuthorization, get } = options; + + + async function setupTestStatus(endpointId: string, userId: string) { + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + const startedAt = record.endpoint.setup.testStartedAt; + if (!startedAt) return { messageReceivedAt: null }; + const links = await db.select({ principalId: chatIdentityLinks.principalId }).from(chatIdentityLinks).where(and( + eq(chatIdentityLinks.companyId, record.endpoint.companyId), eq(chatIdentityLinks.endpointId, endpointId), + eq(chatIdentityLinks.paperclipUserId, userId), eq(chatIdentityLinks.status, "linked"), + )); + if (!links.length) return { messageReceivedAt: null }; + const principalIds = links.map((link) => link.principalId); + const [delivery, command] = await Promise.all([ + db.select({ at: chatDeliveries.createdAt }).from(chatDeliveries).where(and( + eq(chatDeliveries.companyId, record.endpoint.companyId), eq(chatDeliveries.endpointId, endpointId), + inArray(chatDeliveries.principalId, principalIds), gte(chatDeliveries.createdAt, new Date(startedAt)), + inArray(chatDeliveries.eventKind, ["message", "mention"]), + )).orderBy(asc(chatDeliveries.createdAt)).limit(1).then((rows) => rows[0]), + db.select({ at: chatActions.createdAt }).from(chatActions).where(and( + eq(chatActions.companyId, record.endpoint.companyId), eq(chatActions.endpointId, endpointId), + inArray(chatActions.principalId, principalIds), eq(chatActions.kind, "slash_task_start"), gte(chatActions.createdAt, new Date(startedAt)), + )).orderBy(asc(chatActions.createdAt)).limit(1).then((rows) => rows[0]), + ]); + const at = [delivery?.at, command?.at].filter((value): value is Date => Boolean(value)).sort((a, b) => a.getTime() - b.getTime())[0]; + return { messageReceivedAt: at?.toISOString() ?? null }; + } + + async function test(endpointId: string, finishOptions?: { optionalSlackTestForUser: string }) { + const initial = await endpointRecord(endpointId); + if (!initial) throw notFound("Chat endpoint not found"); + return withCredentialMutationLease( + initial.endpoint, + async (credentialLease) => { + // Reload only after owning the same lane as reconnect, pause, removal, + // and credential rotation. Qualification and activation must describe + // one exact provider runtime, never a setup snapshot that a concurrent + // reconnect has already superseded. + const record = await endpointRecord(endpointId); + if (!record) throw notFound("Chat endpoint not found"); + const endpoint = record.endpoint; + if (endpoint.status !== "verifying" || endpoint.setup.step !== "test") { + throw conflict("This connection is not waiting for a setup test", { + code: "chat_endpoint_not_testing", + }); + } + if (endpoint.provider === "github" && endpoint.setup.github) { + const verified = await githubChatManagementService(db, fetchImpl).verification(endpoint.id); + if (!verified.ready && !(finishOptions?.optionalSlackTestForUser && verified.connectionReady)) throw conflict("Finish verifying the App, repositories, and assigned agent's tools before activating this bot", { checks: verified.checks }); + } + const testStartedAtValue = endpoint.setup.testStartedAt; + const testStartedAt = testStartedAtValue + ? new Date(testStartedAtValue) + : null; + const optionalSlackTest = Boolean(finishOptions?.optionalSlackTestForUser); + if (optionalSlackTest) { + if (!["slack", "github"].includes(endpoint.provider) || !endpoint.setup.webhookVerifiedAt || !endpoint.providerAccountId) { + throw conflict("Verify the connection before finishing setup"); + } + const linked = await findAuthorizedIdentityLink(endpoint, finishOptions!.optionalSlackTestForUser); + if (!linked) throw forbidden("Connect your account before finishing setup"); + } + if (!optionalSlackTest) { + if ( + !endpoint.lastEventAt || + !testStartedAtValue || + !testStartedAt || + Number.isNaN(testStartedAt.getTime()) || + endpoint.lastEventAt < testStartedAt + ) { + throw conflict( + "Send the test message in the provider before completing setup", + { + code: "chat_test_message_missing", + }, + ); + } + const requiredTrigger = + ["telegram", "imessage-photon"].includes(endpoint.provider) + ? "direct_message" + : "subscribed_message"; + const qualifyingDelivery = await db + .select({ + id: chatDeliveries.id, + conversationId: chatDeliveries.conversationId, + processedAt: chatDeliveries.processedAt, + }) + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.companyId, endpoint.companyId), + eq(chatDeliveries.endpointId, endpoint.id), + eq(chatDeliveries.state, "processed"), + gte(chatDeliveries.processedAt, testStartedAt), + sql`${chatDeliveries.normalizedEvent}->>'trigger' = ${requiredTrigger}`, + ), + ) + .orderBy(desc(chatDeliveries.processedAt)) + .limit(1) + .then((rows) => rows[0] ?? null); + if ( + !qualifyingDelivery?.conversationId || + !qualifyingDelivery.processedAt + ) { + throw conflict( + ["telegram", "imessage-photon"].includes(endpoint.provider) + ? "Send the test direct message before completing setup" + : "Reply once without mentioning the agent before completing setup", + { code: "chat_test_follow_up_missing" }, + ); + } + const finalPublication = await db + .select({ + commentId: chatPublications.commentId, + payload: chatPublications.payload, + }) + .from(chatPublications) + .innerJoin( + issueComments, + and( + eq(issueComments.id, chatPublications.commentId), + eq(issueComments.companyId, chatPublications.companyId), + eq(issueComments.issueId, chatPublications.issueId), + eq(issueComments.authorType, "agent"), + eq(issueComments.authorAgentId, endpoint.assignedAgentId), + ), + ) + .innerJoin( + chatMessageLinks, + and( + eq(chatMessageLinks.companyId, chatPublications.companyId), + eq(chatMessageLinks.endpointId, chatPublications.endpointId), + eq( + chatMessageLinks.conversationId, + chatPublications.conversationId, + ), + eq(chatMessageLinks.deliveryId, qualifyingDelivery.id), + eq(chatMessageLinks.direction, "inbound"), + isNotNull(chatMessageLinks.commentId), + ), + ) + .innerJoin( + heartbeatRuns, + and( + eq(heartbeatRuns.id, issueComments.createdByRunId), + eq(heartbeatRuns.companyId, chatPublications.companyId), + eq(heartbeatRuns.agentId, endpoint.assignedAgentId), + eq(heartbeatRuns.status, "succeeded"), + eq( + sql`${heartbeatRuns.contextSnapshot} ->> 'issueId'`, + sql`${chatPublications.issueId}::text`, + ), + or( + sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot} ->> 'wakeCommentId'`, + sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot} ->> 'commentId'`, + sql`coalesce(${heartbeatRuns.contextSnapshot} -> 'wakeCommentIds', '[]'::jsonb) ? ${chatMessageLinks.commentId}::text`, + ), + ), + ) + .where( + and( + eq(chatPublications.companyId, endpoint.companyId), + eq(chatPublications.endpointId, endpoint.id), + eq( + chatPublications.conversationId, + qualifyingDelivery.conversationId, + ), + eq(chatPublications.state, "published"), + gte(chatPublications.publishedAt, qualifyingDelivery.processedAt), + ), + ) + .orderBy(desc(chatPublications.publishedAt)) + .then((rows) => + rows.find( + (row) => + row.payload.interactionId === undefined && + row.payload.progressState === undefined && + row.commentId !== null, + ), + ); + let githubToolReply = false; + if (!finalPublication && endpoint.provider === "github") { + const setupRuns = await db.select({ id: heartbeatRuns.id, issueId: chatConversations.issueId }) + .from(heartbeatRuns) + .innerJoin(chatMessageLinks, and( + eq(chatMessageLinks.companyId, endpoint.companyId), + eq(chatMessageLinks.endpointId, endpoint.id), + eq(chatMessageLinks.deliveryId, qualifyingDelivery.id), + eq(chatMessageLinks.direction, "inbound"), + or( + sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot}->>'wakeCommentId'`, + sql`coalesce(${heartbeatRuns.contextSnapshot}->'wakeCommentIds', '[]'::jsonb) ? ${chatMessageLinks.commentId}::text`, + ), + )) + .innerJoin(chatConversations, and( + eq(chatConversations.id, qualifyingDelivery.conversationId), + eq(chatConversations.id, chatMessageLinks.conversationId), + eq(chatConversations.companyId, endpoint.companyId), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${chatConversations.issueId}::text`, + )) + .where(and(eq(heartbeatRuns.companyId, endpoint.companyId), + eq(heartbeatRuns.agentId, endpoint.assignedAgentId), eq(heartbeatRuns.status, "succeeded"))) + .orderBy(desc(heartbeatRuns.createdAt)).limit(20); + for (const run of setupRuns) { + if (await githubRunReplyState(db, { companyId: endpoint.companyId, endpointId: endpoint.id, + issueId: run.issueId, runId: run.id }) === "confirmed") { + githubToolReply = true; + break; + } + } + } + if (!finalPublication && !githubToolReply) { + throw conflict( + "Wait for the Paperclip agent to reply to the setup turn before completing setup", + { + code: "chat_test_round_trip_incomplete", + }, + ); + } + } + await options.setupTestActivationBarrier?.(); + const expectedGeneration = runtimeGeneration(endpoint.setup); + await db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + if (optionalSlackTest) { + const linked = await tx.select({ principalId: chatIdentityLinks.principalId }).from(chatIdentityLinks).where(and( + eq(chatIdentityLinks.endpointId, endpoint.id), eq(chatIdentityLinks.paperclipUserId, finishOptions!.optionalSlackTestForUser), eq(chatIdentityLinks.status, "linked"), + )).limit(1).then((rows) => rows[0]); + if (!linked || !(await lockCurrentPrincipalAuthorization(tx, endpoint, linked.principalId)).allowed) throw forbidden("Your connected account is no longer authorized"); + } + await notifyChatEndpointWork(tx); + const [activated] = await tx + .update(chatEndpoints) + .set({ + status: "active", + setup: { + ...endpoint.setup, + step: "complete", + testStartedAt: null, + testSkipped: optionalSlackTest, + }, + healthMessage: optionalSlackTest ? "Connected; conversation test optional" : "Connected", + activatedAt: endpoint.activatedAt ?? new Date(), + updatedAt: new Date(), + }) + .where( + and( + eq(chatEndpoints.id, endpointId), + eq(chatEndpoints.status, "verifying"), + sql`${chatEndpoints.setup}->>'step' = 'test'`, + sql`coalesce((${chatEndpoints.setup}->>'runtimeGeneration')::integer, 0) = ${expectedGeneration}`, + sql`${chatEndpoints.setup}->>'testStartedAt' = ${testStartedAtValue}`, + ), + ) + .returning({ id: chatEndpoints.id }); + if (!activated) { + throw conflict("This setup test was superseded by a reconnect", { + code: "chat_endpoint_test_superseded", + }); + } + await tx + .update(toolConnections) + .set({ + status: "active", + enabled: true, + healthStatus: "healthy", + healthMessage: optionalSlackTest ? "Connected; conversation test optional" : "Connected", + lastError: null, + healthCheckedAt: new Date(), + updatedAt: new Date(), + }) + .where(eq(toolConnections.id, endpoint.connectionId)); + if (optionalSlackTest) await logActivity(tx as unknown as Db, { + companyId: endpoint.companyId, actorType: "user", actorId: finishOptions!.optionalSlackTestForUser, + action: "chat.setup_completed", entityType: "chat_endpoint", entityId: endpointId, + details: { conversationTestOptional: true }, + }); + await credentialLease.assertOwned(tx); + }); + return get(endpointId); + }, + ); + } + return { setupTestStatus, test }; +} diff --git a/server/src/services/chat-channels/slack/callback-verification.ts b/server/src/services/chat-channels/slack/callback-verification.ts new file mode 100644 index 00000000000..ecddb2f665f --- /dev/null +++ b/server/src/services/chat-channels/slack/callback-verification.ts @@ -0,0 +1,300 @@ +import { isProvisionedSlackSetup } from "@paperclipai/shared"; +import type { EndpointRow, DbOrTransaction, SlackCallbackSurface, InternalSetupState } from "../runtime-types.js"; + +import { notifyChatVerificationWork } from "../../chat-work-notifications.js"; + +import { slackChatRegistrationService } from "../../chat-slack-registration.js"; +import { chatSlackRegistrations } from "@paperclipai/db"; + +import { runtimeCanonicalOrigin } from "../../cloud-runtime-identity.js"; + +import { + createHash, + createHmac, + timingSafeEqual, +} from "node:crypto"; +import { + and, + eq, +} from "drizzle-orm"; + +import type { Db } from "@paperclipai/db"; + +import { + chatEndpoints, + toolConnections, +} from "@paperclipai/db"; +import type { + ToolCredentialSecretRef, +} from "@paperclipai/shared"; + +import { + logActivity, +} from "../../activity-log.js"; + +import { secretService } from "../../secrets.js"; + +function slackRequestSignatureIsValid( + request: Request, + body: string, + signingSecret: string, +): boolean { + const timestamp = request.headers.get("x-slack-request-timestamp"); + const signature = request.headers.get("x-slack-signature"); + if (!timestamp || !signature) return false; + const timestampSeconds = Number(timestamp); + if ( + !Number.isFinite(timestampSeconds) || + Math.abs(Math.floor(Date.now() / 1000) - timestampSeconds) > 300 + ) + return false; + const expected = `v0=${createHmac("sha256", signingSecret).update(`v0:${timestamp}:${body}`).digest("hex")}`; + try { + return timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); + } catch { + return false; + } +} + +function slackRequestWorkspaceId( + body: string, + contentType: string, +): string | null { + let payload: unknown; + try { + if (contentType.includes("application/json")) { + payload = JSON.parse(body); + } else { + const form = new URLSearchParams(body); + const interactivePayload = form.get("payload"); + payload = interactivePayload + ? JSON.parse(interactivePayload) + : Object.fromEntries(form.entries()); + } + } catch { + return null; + } + if (!payload || typeof payload !== "object") return null; + const record = payload as Record; + const nestedId = (value: unknown): string | null => { + if (!value || typeof value !== "object") return null; + const id = (value as Record).id; + return typeof id === "string" && id.length > 0 ? id : null; + }; + const stringField = (key: string): string | null => { + const value = record[key]; + return typeof value === "string" && value.length > 0 ? value : null; + }; + const authorization = Array.isArray(record.authorizations) + ? record.authorizations.find( + (value): value is Record => + Boolean(value) && typeof value === "object", + ) + : null; + const authorizationField = (key: string): string | null => { + const value = authorization?.[key]; + return typeof value === "string" && value.length > 0 ? value : null; + }; + + // A Grid event may carry both an enterprise id and the concrete workspace + // id. The bot identity is verified against auth.test's team_id, so prefer a + // team id and use the enterprise id only when Slack omitted team context. + return ( + stringField("team_id") ?? + nestedId(record.team) ?? + authorizationField("team_id") ?? + stringField("enterprise_id") ?? + nestedId(record.enterprise) ?? + authorizationField("enterprise_id") + ); +} + +function canonicalCallbackUrl(value: string): string | null { + try { + const url = new URL(value); + if (url.protocol !== "https:" && url.protocol !== "http:") return null; + url.hostname = url.hostname.replace(/\.$/, ""); + url.username = ""; + url.password = ""; + url.search = ""; + url.hash = ""; + return url.toString(); + } catch { + return null; + } +} + +// TLS terminates at a reverse proxy in many self-hosted deployments. The +// transport scheme is not evidence of URL drift; authority and path still are. +// This is diagnostic only: it does not trust forwarded headers or change auth. +function slackCallbackMatchesPublicUrl(observed: string, current: string): boolean { + const canonical = canonicalCallbackUrl(observed); + if (canonical === current) return true; + if (!canonical) return false; + const observedUrl = new URL(canonical); + const currentUrl = new URL(current); + return observedUrl.protocol === "http:" + && currentUrl.protocol === "https:" + && observedUrl.host === currentUrl.host + && observedUrl.pathname === currentUrl.pathname; +} + +async function inspectSlackCallback( + request: Request, +): Promise { + const url = slackCallbackObservationUrl(request); + if (!url) return null; + const contentType = request.headers.get("content-type")?.toLowerCase() ?? ""; + let body: string; + try { + body = await request.text(); + } catch { + return null; + } + if (contentType.includes("application/json")) { + try { + const payload = JSON.parse(body) as { + type?: unknown; + challenge?: unknown; + }; + const isUrlVerification = + payload.type === "url_verification" && + typeof payload.challenge === "string" && + payload.challenge.length > 0; + if (isUrlVerification || payload.type === "event_callback") { + return { + surface: "events", + url, + isUrlVerification, + }; + } + } catch { + return null; + } + return null; + } + + const form = new URLSearchParams(body); + if (form.get("command")) { + return { surface: "slashCommands", url, isUrlVerification: false }; + } + const interactivePayload = form.get("payload"); + if (!interactivePayload) return null; + try { + const payload = JSON.parse(interactivePayload) as { type?: unknown }; + if ( + typeof payload.type === "string" && + [ + "block_actions", + "block_suggestion", + "dialog_submission", + "message_action", + "shortcut", + "view_closed", + "view_submission", + "workflow_step_edit", + ].includes(payload.type) + ) { + return { surface: "interactivity", url, isUrlVerification: false }; + } + } catch { + return null; + } + return null; +} + +type SlackCallbackInspection = { + surface: SlackCallbackSurface; + url: string; + isUrlVerification: boolean; +}; + +// Cloud records the public request host in dedicated headers so provider edges +// cannot replace it with their upstream host. Only use callback-health evidence on a +// claimed Cloud instance, after provider authentication succeeded. It must not +// change the Request passed to the adapter, signature verification, routing, +// board identity, or the configured URL used to generate callbacks. +function slackCallbackObservationUrl(request: Request): string | null { + const directUrl = canonicalCallbackUrl(request.url); + if (!directUrl || !runtimeCanonicalOrigin()) return directUrl; + const hasCloudEvidence = request.headers.has("x-paperclip-cloud-forwarded-host"); + const host = request.headers.get(hasCloudEvidence ? "x-paperclip-cloud-forwarded-host" : "x-forwarded-host")?.trim(); + const protocol = request.headers.get(hasCloudEvidence ? "x-paperclip-cloud-forwarded-proto" : "x-forwarded-proto")?.trim(); + if (!host || /[\s/@\\?#,]/.test(host) || (protocol !== "https" && protocol !== "http")) return directUrl; + try { + const origin = new URL(`${protocol}://${host}`); + // Accept one authority only, never credentials, paths, queries, or lists. + if (!origin.host || origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) return directUrl; + const observed = new URL(directUrl); + observed.protocol = origin.protocol; + observed.host = origin.host; + observed.port = origin.port; + return canonicalCallbackUrl(observed.toString()); + } catch { + return directUrl; + } +} +export { slackRequestSignatureIsValid, slackRequestWorkspaceId, canonicalCallbackUrl, slackCallbackMatchesPublicUrl, inspectSlackCallback }; + +type SlackCallbackVerificationOptions = { + resolveCredentialRefs: (endpoint: EndpointRow, refs: ToolCredentialSecretRef[], database?: DbOrTransaction) => Promise>; + getWebhookPublicBaseUrl: () => string | null; + scheduleMessageProcessing: (task: () => Promise) => void; + slackRegistration: Pick, "notifyVerified">; +}; + +export function createSlackCallbackVerification(db: Db, options: SlackCallbackVerificationOptions) { + const { resolveCredentialRefs, getWebhookPublicBaseUrl, scheduleMessageProcessing, slackRegistration } = options; + + + async function verifySlackRequestUrl(endpoint: EndpointRow, request: Request): Promise { + const callback = await inspectSlackCallback(request.clone()); + if (!callback?.isUrlVerification) return null; + const body = await request.clone().text(); + let notifyAccount = false; + // URL verification needs only the signing secret. Do not initialize the + // SDK (which calls Slack auth.test), wait for OAuth, or acquire the creation + // lease: Slack can deliver this check while app setup still owns that lease. + const response = await db.transaction(async tx => { + const [current] = await tx.select().from(chatEndpoints).where(eq(chatEndpoints.id, endpoint.id)).for("no key update"); + if (!current || ["archived", "paused", "revoked"].includes(current.status)) return new Response("ignored", { status: 200 }); + const [connection] = await tx.select({ status: toolConnections.status, refs: toolConnections.credentialSecretRefs }).from(toolConnections) + .where(and(eq(toolConnections.id, current.connectionId), eq(toolConnections.companyId, current.companyId))); + if (!connection || connection.status === "archived") return new Response("ignored", { status: 200 }); + let secret = (await resolveCredentialRefs(current, connection.refs.filter(ref => ref.configPath.replace(/^credentials\./, "") === "signingSecret"), tx)).signingSecret; + if (!secret && isProvisionedSlackSetup(current.setup.slackSetupMethod)) { + const [registration] = await tx.select().from(chatSlackRegistrations) + .where(and(eq(chatSlackRegistrations.endpointId, current.id), eq(chatSlackRegistrations.companyId, current.companyId))); + if (registration?.status !== "removed" && registration?.secretIds.signingSecret) { + secret = await secretService(tx as unknown as Db).resolveSecretValue(current.companyId, registration.secretIds.signingSecret, "latest", { + accessContext: { consumerType: "system", consumerId: `slack-registration:${current.id}`, configPath: "slack_registration.signingSecret", actorType: "system" }, + }); + } + } + if (!secret) return new Response("Slack signing secret is not ready yet", { status: 503 }); + if (!slackRequestSignatureIsValid(request, body, secret)) return new Response("Invalid signature", { status: 401 }); + const workspaceId = slackRequestWorkspaceId(body, request.headers.get("content-type") ?? ""); + if (workspaceId && current.providerAccountId && workspaceId !== current.providerAccountId) return new Response("ignored", { status: 200 }); + const observedAt = new Date(); + const setup = current.setup as InternalSetupState; + const matchesCurrentUrl = slackCallbackMatchesPublicUrl(callback.url, `${getWebhookPublicBaseUrl()}/api/chat-webhooks/${current.publicId}/slack`); + await notifyChatVerificationWork(tx); + await tx.update(chatEndpoints).set({ setup: { + ...setup, + ...(matchesCurrentUrl ? { webhookVerifiedAt: setup.webhookVerifiedAt ?? observedAt.toISOString(), + slackVerificationSigningFingerprint: createHash("sha256").update(secret).digest("hex") } : {}), + slackCallbackSurfaces: { ...setup.slackCallbackSurfaces, events: { url: callback.url, observedAt: observedAt.toISOString() } }, + ...(matchesCurrentUrl && setup.slackAccount?.status === "linked" && !setup.slackAccount.verificationStatus + ? { slackAccount: { ...setup.slackAccount, verificationStatus: "pending" as const } } : {}), + } as InternalSetupState, ...(matchesCurrentUrl ? { healthMessage: "Slack Events Request URL verified" } : {}), updatedAt: observedAt }).where(eq(chatEndpoints.id, current.id)); + if (matchesCurrentUrl && !setup.webhookVerifiedAt) await logActivity(tx as unknown as Db, { companyId: current.companyId, actorType: "system", actorId: "slack-webhook", + action: "chat_endpoint.webhook_verified", entityType: "tool_connection", entityId: current.connectionId, + details: { endpointId: current.id, provider: "slack" } }); + notifyAccount = matchesCurrentUrl && setup.slackAccount?.status === "linked" && !setup.slackAccount.verificationStatus; + return Response.json({ challenge: (JSON.parse(body) as { challenge: string }).challenge }); + }); + if (notifyAccount) scheduleMessageProcessing(() => slackRegistration.notifyVerified(endpoint.id)); + return response; + } + return { verifySlackRequestUrl }; +} diff --git a/server/src/services/chat-channels/slack/messages.ts b/server/src/services/chat-channels/slack/messages.ts index eba1e833f2f..dece4360827 100644 --- a/server/src/services/chat-channels/slack/messages.ts +++ b/server/src/services/chat-channels/slack/messages.ts @@ -41,3 +41,49 @@ export function slackMessageSentAt(raw: unknown, messageId: string): Date | null const date = new Date(Number(ts) * 1_000); return Number.isFinite(date.getTime()) ? date : null; } + +export function isSlackReceiptReactionAlreadyApplied(error: unknown): boolean { + const pending: Array<{ depth: number; value: unknown }> = [ + { depth: 0, value: error }, + ]; + const seen = new Set(); + while (pending.length > 0) { + const current = pending.shift(); + if ( + !current || + current.depth > 4 || + !current.value || + typeof current.value !== "object" || + seen.has(current.value) + ) { + continue; + } + seen.add(current.value); + const record = current.value as { + cause?: unknown; + code?: unknown; + data?: { error?: unknown }; + original?: unknown; + originalError?: unknown; + }; + if ( + record.code === "already_reacted" || + record.data?.error === "already_reacted" + ) { + return true; + } + for (const value of [record.cause, record.original, record.originalError]) { + pending.push({ depth: current.depth + 1, value }); + } + } + return false; +} + +export function slackThreadChannelId(threadId: string): string | null { + if (!threadId.startsWith("slack:")) return null; + const channelAndThread = threadId.slice("slack:".length); + const separator = channelAndThread.indexOf(":"); + const channelId = + separator === -1 ? channelAndThread : channelAndThread.slice(0, separator); + return channelId || null; +} diff --git a/server/src/services/chat-channels/slack/task-start.ts b/server/src/services/chat-channels/slack/task-start.ts index 0ccb3793616..309953a69f9 100644 --- a/server/src/services/chat-channels/slack/task-start.ts +++ b/server/src/services/chat-channels/slack/task-start.ts @@ -1073,3 +1073,21 @@ export function createSlackTaskStart(db: Db, options: SlackTaskStartOptions) { } return { latestSlackDmControlThreadId, finalizeSlackTaskStartFailure, admitConfirmedSlackTaskStart, processSlackTaskStart, processPendingSlackTaskStarts }; } + +export function slackTaskStartActivityStatus(action: { + status: string; + updatedAt: Date; +}): string { + const age = Date.now() - action.updatedAt.getTime(); + if (action.status === "validating" && age >= SLACK_COMMAND_POST_STALE_MS) { + return "queued"; + } + if ( + (action.status === "received" && age >= SLACK_COMMAND_POST_STALE_MS) || + (action.status === "resolving" && + age >= SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS) + ) { + return "delivery_unknown"; + } + return action.status; +} diff --git a/server/src/services/chat-channels/telegram/callback-notices.ts b/server/src/services/chat-channels/telegram/callback-notices.ts new file mode 100644 index 00000000000..fe926e033b1 --- /dev/null +++ b/server/src/services/chat-channels/telegram/callback-notices.ts @@ -0,0 +1,128 @@ +import type { EndpointRow, DbTransaction, DbOrTransaction } from "../runtime-types.js"; + +import { + createHash, +} from "node:crypto"; +import { + and, + eq, +} from "drizzle-orm"; + +import { + chatEndpointResources, + chatExternalPrincipals, + chatIdentityLinks, +} from "@paperclipai/db"; + +import { + type TelegramCallbackReceipt, +} from "../../chat-telegram-ephemeral.js"; + +type TelegramCallbackNoticeAuthorityOptions = { + nonDirectDestinationAllowed: (endpoint: EndpointRow, resource: typeof chatEndpointResources.$inferSelect | null | undefined) => boolean; + lockCurrentPrincipalAuthorization: (tx: DbOrTransaction, endpoint: EndpointRow, principalId: string) => Promise<{ allowed: boolean; linkedDenied: boolean; userId: string | null; sponsorUserId?: string | null }>; +}; + +export function createTelegramCallbackNoticeAuthority(options: TelegramCallbackNoticeAuthorityOptions) { + const { nonDirectDestinationAllowed, lockCurrentPrincipalAuthorization } = options; + + + // This is permission to return one fixed, neutral callback denial, not + // permission to execute the denied action or publish to the conversation. + // Retain the initial identity-policy snapshot so queued notices cannot cross + // a subsequent actor/link/reach change. No provider I/O occurs under locks. + async function lockTelegramCallbackNoticeAuthority( + tx: DbTransaction, + endpoint: EndpointRow, + receipt: TelegramCallbackReceipt, + principalId: string | null, + ): Promise { + if ( + endpoint.provider !== "telegram" || + endpoint.status !== "active" || + endpoint.companyId !== receipt.companyId || + endpoint.id !== receipt.endpointId || + endpoint.botExternalId !== receipt.botUserId || + !endpoint.providerAccountId || + !principalId || + Date.now() < receipt.receivedAtMs || + Date.now() >= receipt.deadlineAtMs + ) + return null; + const resource = + receipt.chatType === "private" + ? null + : await tx + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.companyId, endpoint.companyId), + eq(chatEndpointResources.endpointId, endpoint.id), + eq(chatEndpointResources.providerResourceId, receipt.chatId), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + if ( + receipt.chatType === "private" + ? !endpoint.allowDirectMessages + : !resource || !nonDirectDestinationAllowed(endpoint, resource) + ) + return null; + const principal = await tx + .select() + .from(chatExternalPrincipals) + .where( + and( + eq(chatExternalPrincipals.companyId, endpoint.companyId), + eq(chatExternalPrincipals.provider, endpoint.provider), + eq( + chatExternalPrincipals.providerAccountId, + endpoint.providerAccountId, + ), + eq(chatExternalPrincipals.id, principalId), + eq(chatExternalPrincipals.externalId, receipt.receiverUserId), + ), + ) + .for("update") + .then((rows) => rows[0] ?? null); + if (!principal || principal.kind !== "user" || principal.isBot) return null; + const authorization = await lockCurrentPrincipalAuthorization( + tx, + endpoint, + principal.id, + ); + const link = await tx + .select({ + status: chatIdentityLinks.status, + userId: chatIdentityLinks.paperclipUserId, + }) + .from(chatIdentityLinks) + .where( + and( + eq(chatIdentityLinks.companyId, endpoint.companyId), + eq(chatIdentityLinks.endpointId, endpoint.id), + eq(chatIdentityLinks.principalId, principal.id), + ), + ) + .then((rows) => rows[0] ?? null); + return createHash("sha256") + .update( + JSON.stringify([ + endpoint.companyId, + endpoint.id, + receipt.botUserId, + resource?.id ?? null, + principal.id, + principal.externalId, + link, + authorization, + endpoint.allowUnlinkedPeople, + endpoint.sponsorUserId, + ]), + ) + .digest("hex"); + } + return { lockTelegramCallbackNoticeAuthority }; +} diff --git a/server/src/services/chat-channels/telegram/messages.ts b/server/src/services/chat-channels/telegram/messages.ts index 152b6478533..ac75997b145 100644 --- a/server/src/services/chat-channels/telegram/messages.ts +++ b/server/src/services/chat-channels/telegram/messages.ts @@ -1,3 +1,4 @@ +import type { Attachment } from "chat"; import { createHash } from "node:crypto"; import type { chatDeliveries } from "@paperclipai/db"; import type { ChatProvider } from "@paperclipai/shared"; @@ -221,3 +222,14 @@ export function telegramMessageSentAt(raw: unknown): Date | null { const sentAt = new Date(value * 1_000); return Number.isFinite(sentAt.getTime()) ? sentAt : null; } + +export function telegramMarkdownAttachment(text: string): Attachment { + const data = Buffer.from(text, "utf8"); + return { + data, + mimeType: "text/markdown; charset=utf-8", + name: "paperclip-response.md", + size: data.byteLength, + type: "file", + }; + } From 4b958ecbd8f33a7782fe6f29ec825f80f636b833 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 15:59:27 -0700 Subject: [PATCH 11/37] fix(server): keep classified provider quota runs out of Sentry (#16029) A run that stops because the user's provider account hit its usage limit is an expected wait, not a Paperclip defect. The adapter already classifies it (errorCode and resultJson.errorFamily are both provider_quota) and recovery schedules a retry at the reset time. Skip the Sentry report only when both adapter-owned fields agree, the run status is failed, no signal stopped it, and the failure did not happen during setup. Timeouts, cancellations, signals, other codes and missing or mismatched families still report. Co-Authored-By: Claude Opus 5.5 Co-Authored-By: Dotta --- .../__tests__/run-failure-report.test.ts | 33 +++++++++++++++++++ server/src/services/run-failure-report.ts | 11 +++++++ 2 files changed, 44 insertions(+) diff --git a/server/src/services/__tests__/run-failure-report.test.ts b/server/src/services/__tests__/run-failure-report.test.ts index 0c1d83de7b7..111be4faf60 100644 --- a/server/src/services/__tests__/run-failure-report.test.ts +++ b/server/src/services/__tests__/run-failure-report.test.ts @@ -301,6 +301,39 @@ describeEmbeddedPostgres("reportRunFailure", () => { expect(mockCaptureRunFailure).toHaveBeenCalledWith(expect.objectContaining({ agentAdapter: "unknown" })); }); + function providerQuotaRun(overrides: Partial = {}) { + return buildRun({ + error: "ACP agent reported a terminal limit failure.\nYou've hit your session limit · resets 2am (UTC)", + errorCode: "provider_quota", executionStage: "dispatching", exitCode: 1, signal: null, + resultJson: { errorFamily: "provider_quota", stopReason: "adapter_failed" }, ...overrides, + }); + } + + it.each(["claude_local", "codex_local"])("keeps a classified %s provider quota wait local", async (adapterType) => { + await seedCompanyAndAgent({ adapterType }); + const [run] = await db.insert(heartbeatRuns).values(providerQuotaRun()).returning(); + const before = structuredClone(run); + for (const phase of [undefined, "execute"] as const) await reportRunFailure(db, run!, { phase }); + expect(mockCaptureRunFailure).not.toHaveBeenCalled(); + expect(await db.select().from(heartbeatRuns)).toEqual([before]); + }); + + it.each([ + { status: "timed_out" }, { status: "cancelled", startedAt: new Date(0) }, { signal: "SIGTERM" }, + { errorCode: "adapter_failed" }, { errorCode: "claude_transient_upstream" }, + { resultJson: {} }, { resultJson: null }, { resultJson: { errorFamily: "transient_upstream" } }, + ])("reports a provider quota outcome without matching adapter evidence: %j", async (overrides) => { + await seedCompanyAndAgent({ adapterType: "claude_local" }); + await reportRunFailure(db, providerQuotaRun(overrides), { phase: "execute" }); + expect(mockCaptureRunFailure).toHaveBeenCalledOnce(); + }); + + it("keeps a provider quota code reportable during setup", async () => { + await seedCompanyAndAgent({ adapterType: "claude_local" }); + await reportRunFailure(db, providerQuotaRun(), { phase: "setup" }); + expect(mockCaptureRunFailure).toHaveBeenCalledOnce(); + }); + function aiSelectionRun(overrides: Partial = {}) { return buildRun({ errorCode: "configuration_incomplete", diff --git a/server/src/services/run-failure-report.ts b/server/src/services/run-failure-report.ts index a059bf523d0..60e6309e541 100644 --- a/server/src/services/run-failure-report.ts +++ b/server/src/services/run-failure-report.ts @@ -164,6 +164,16 @@ function isHermesConnectionFailure(run: HeartbeatRun, options: RunFailureReportO (connection.reason === "remote_unavailable" && code === "hermes_gateway_upstream_error"); } +/** + * The adapter proved the user's provider account is out of quota, and recovery + * waits for its reset. Both fields come from the adapter's own classifier. + */ +function isProviderQuotaFailure(run: HeartbeatRun, options: RunFailureReportOptions): boolean { + return run.status === "failed" && run.errorCode === "provider_quota" && + asRecord(run.resultJson)?.errorFamily === "provider_quota" && + options.phase !== "setup" && run.signal == null; +} + /** * Report a terminal run failure to Sentry. Returns at once for any status * other than failures and unexpected started cancellations. Never throws — a Sentry failure or a @@ -182,6 +192,7 @@ export function reportRunFailure(db: Db, run: HeartbeatRun, options: RunFailureR if (isAiConnectionSelectionBlocker(run, options)) return Promise.resolve(); if (isLocalPathWorkspaceConfigurationBlocker(run, options)) return Promise.resolve(); if (isGitConnectionFailure(run, options)) return Promise.resolve(); + if (isProviderQuotaFailure(run, options)) return Promise.resolve(); const runStatus = run.status; const report = captureTerminalRunFailure(db, run, runStatus, options); pendingRunFailureReports.add(report); From f329b24ee12496e710ad17377ec7716e0eee22ef Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 20:45:18 -0500 Subject: [PATCH 12/37] feat: give tasks durable project-optional workspaces (#15749) Give tasks durable company-scoped workspace bindings independent of project assignment, channel defaults and authorized repository preparation. Reuse verified input seeds and sparse output recovery while preserving trust, privacy and controller provisioning boundaries. Full CI and E2E, Superagent, legacy checks and Greptile Apex 5/5 passed on reviewed head 753680b61c3fc51f7970fc0074f79e97d65fd9fc. All review findings are resolved; the PR records focused verification and historical acceptance limits. Co-Authored-By: Paperclip --- doc/DATABASE.md | 45 + doc/DEVELOPING.md | 8 + doc/SPEC-implementation.md | 11 + doc/SPEC.md | 7 + doc/evals.md | 6 + doc/observability.md | 22 +- doc/plans/2026-10-09-task-workspaces.md | 153 + doc/run-log-events.md | 35 + maps/agent-access-map/chat-channels.md | 12 + maps/agent-access-map/workspaces.md | 58 + maps/feature-map/chat-channels.md | 5 + maps/feature-map/workspaces.md | 38 + maps/storybook-map/INDEX.md | 52 +- maps/storybook-map/REVIEW.md | 55 + maps/storybook-map/catalog.json | 144 +- .../src/command-managed-runtime.ts | 17 +- .../adapter-utils/src/execution-target.ts | 13 +- .../adapter-utils/src/git-workspace-sync.ts | 51 +- .../src/sandbox-callback-bridge.test.ts | 9 +- .../src/sandbox-managed-deletions.test.ts | 79 + .../src/sandbox-managed-runtime.test.ts | 5 + .../src/sandbox-managed-runtime.ts | 268 +- .../src/sandbox-managed-seed.test.ts | 113 + packages/adapter-utils/src/ssh.ts | 2 +- .../src/workspace-checkpoint.test.ts | 455 + .../adapter-utils/src/workspace-checkpoint.ts | 163 + .../src/workspace-restore-merge.test.ts | 37 + .../src/workspace-restore-merge.ts | 36 +- .../src/workspace-seed-cache.test.ts | 114 + .../adapter-utils/src/workspace-seed-cache.ts | 134 + .../db/src/migrations/0336_big_crystal.sql | 48 + .../migrations/0337_exotic_proemial_gods.sql | 16 + .../src/migrations/0338_absurd_vengeance.sql | 20 + .../db/src/migrations/meta/0336_snapshot.json | 59363 +++++++++++++++ .../db/src/migrations/meta/0337_snapshot.json | 59457 +++++++++++++++ .../db/src/migrations/meta/0338_snapshot.json | 59587 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 21 + packages/db/src/schema/chat_channels.ts | 3 + .../execution_workspace_repositories.ts | 29 + .../execution_workspace_repository_access.ts | 18 + .../execution_workspace_repository_roots.ts | 14 + .../db/src/schema/execution_workspaces.ts | 2 +- packages/db/src/schema/index.ts | 3 + packages/db/src/schema/issues.ts | 5 +- packages/paperclip-runner/README.md | 5 + .../docs/capability-contract.md | 44 +- .../generated/capability/capabilities.yaml | 90 +- .../capability/capability-contract.md | 4 +- .../capability/semantic-tool-contracts.json | 2 +- .../generated/semantic-action-catalog.json | 230 + .../evals/native-execution-seeded.json | 2 +- .../paperclip-runner/protocol/manifest.json | 2 +- .../check-capability-inventory.test.mjs | 2 +- .../scripts/lib/capability-inventory.mjs | 2 +- .../spec/capability/capabilities.yaml | 144 +- .../spec/capability/protocol-coverage.json | 78 +- .../spec/operation-groups/source.json | 8 +- .../spec/paperclip-agent-operation-groups.md | 18 +- .../src/catalog/reconciliation.test.ts | 14 +- .../catalog/semantic-action-catalog.test.ts | 2 +- .../src/catalog/semantic-action-catalog.ts | 8 + .../src/catalog/semantic-action-types.ts | 5 + .../drivers/acpx/permission-policy.test.ts | 10 + .../src/drivers/acpx/permission-policy.ts | 3 +- .../src/eval/workflow-evals.test.ts | 6 +- .../src/generated/capability-contract.ts | 4 +- .../src/protocol-actions/get-workspace.ts | 72 + .../src/protocol-actions/index.ts | 9 + .../src/protocol-actions/list-workspaces.ts | 75 + .../protocol-actions/prepare-repository.ts | 122 + .../src/protocol-actions/select-workspace.ts | 141 + .../src/semantic-tools/discovery.ts | 4 +- .../src/semantic-tools/dispatcher.ts | 4 + .../src/semantic-tools/paperclip-discovery.ts | 2 +- .../src/semantic-tools/semantic-tools.test.ts | 4 +- .../src/semantic-tools/types.ts | 4 + packages/plugins/sdk/src/types.ts | 3 +- packages/shared/src/index.ts | 5 + packages/shared/src/types/chat-channels.ts | 10 + packages/shared/src/types/index.ts | 2 + packages/shared/src/types/issue.ts | 4 + packages/shared/src/types/task-workspace.ts | 16 + .../shared/src/types/workspace-runtime.ts | 8 +- .../shared/src/validators/chat-channels.ts | 10 +- packages/shared/src/validators/index.ts | 2 + packages/shared/src/validators/issue.ts | 3 + .../shared/src/validators/task-workspace.ts | 15 + .../src/validators/workspace-repository.ts | 11 + .../__tests__/agent-directory-probe.test.ts | 47 +- .../chat-channels.integration.test.ts | 172 +- .../__tests__/chat-execution-defaults.test.ts | 32 + .../src/__tests__/chat-project-tools.test.ts | 12 +- .../src/__tests__/durable-chat-wakeup.test.ts | 7 +- .../email-channels.integration.test.ts | 58 + .../execution-workspace-reopen.test.ts | 190 +- .../execution-workspaces-routes.test.ts | 1 + .../execution-workspaces-service.test.ts | 36 +- server/src/__tests__/file-resources.test.ts | 283 +- ...at-accepted-plan-workspace-refresh.test.ts | 19 + .../heartbeat-comment-wake-batching.test.ts | 18 + .../heartbeat-dependency-scheduling.test.ts | 14 + ...eartbeat-issue-liveness-escalation.test.ts | 25 +- ...eartbeat-managed-clone-credentials.test.ts | 71 + ...heartbeat-native-cleanup-admission.test.ts | 7 +- .../heartbeat-plugin-environment.test.ts | 16 + .../heartbeat-process-recovery.test.ts | 81 +- .../heartbeat-project-repositories.test.ts | 658 +- ...artbeat-responsible-user-invariant.test.ts | 29 +- .../heartbeat-session-preparation.test.ts | 8 +- .../heartbeat-slack-invitation.test.ts | 6 +- ...tbeat-workspace-branch-containment.test.ts | 29 +- .../heartbeat-workspace-busy.test.ts | 20 +- ...eartbeat-workspace-finalize-branch.test.ts | 11 +- .../heartbeat-workspace-session.test.ts | 62 +- .../heartbeat-workspace-source-trust.test.ts | 230 + .../__tests__/helpers/isolated-test-home.ts | 31 + .../helpers/repository-trust-fixture.ts | 114 + .../src/__tests__/isolated-test-home.test.ts | 62 + .../issue-activity-events-routes.test.ts | 1 + .../issue-closed-workspace-routes.test.ts | 1 + server/src/__tests__/issues-service.test.ts | 4 +- .../legacy-finalization-regression.test.ts | 9 + .../managed-repository-checkout.test.ts | 71 + .../native-session-resumption.test.ts | 9 +- .../native-workspace-sync-history.test.ts | 36 +- .../__tests__/native-workspace-sync.test.ts | 4 + server/src/__tests__/openapi-routes.test.ts | 1 + server/src/__tests__/pipelines-routes.test.ts | 17 +- .../project-workspace-root-provenance.test.ts | 91 + server/src/__tests__/remove-test-home.test.ts | 2 +- server/src/__tests__/remove-test-home.ts | 6 +- server/src/__tests__/run-trust-preset.test.ts | 5 +- server/src/__tests__/setup-supertest.ts | 8 +- .../task-workspace-admission.test.ts | 37 + .../src/__tests__/task-workspace-api.test.ts | 167 + .../__tests__/task-workspace-binding.test.ts | 884 + .../task-workspace-child-trust.test.ts | 81 + ...workspace-repositories-persistence.test.ts | 247 + .../task-workspace-repositories-trust.test.ts | 853 + .../task-workspace-repositories.test.ts | 34 + ...task-workspace-source-copy-privacy.test.ts | 732 + .../__tests__/task-workspace-tools.test.ts | 29 + .../__tests__/trust-preset-resolver.test.ts | 13 + .../workspace-host-provision.test.ts | 68 + .../src/__tests__/workspace-runtime.test.ts | 104 + server/src/app.ts | 2 + server/src/redaction.ts | 2 + server/src/routes/execution-workspaces.ts | 5 +- server/src/routes/issues.ts | 28 +- server/src/routes/openapi.ts | 31 + server/src/routes/task-workspaces.ts | 51 + .../agent-directory-working-copies.ts | 2 +- server/src/services/agent-file-checkpoints.ts | 2 +- server/src/services/authorization.ts | 25 +- server/src/services/chat-channels.ts | 47 +- .../chat-channels/resource-reconciliation.ts | 39 +- .../src/services/chat-execution-defaults.ts | 69 + server/src/services/company-deletion.ts | 2 + server/src/services/email-channels.ts | 37 +- .../services/execution-workspace-policy.ts | 42 +- .../execution-workspace-repositories.ts | 922 + .../execution-workspace-source-roots.ts | 164 + server/src/services/execution-workspaces.ts | 592 +- server/src/services/heartbeat.ts | 149 +- .../heartbeat/environment-selection.test.ts | 34 +- .../heartbeat/environment-selection.ts | 78 +- .../heartbeat/execution-configuration.ts | 35 +- .../heartbeat/runtime-execution.test.ts | 22 + .../services/heartbeat/runtime-execution.ts | 359 +- .../heartbeat/runtime-selection.test.ts | 4 + .../services/heartbeat/runtime-selection.ts | 4 + .../services/heartbeat/runtime-services.ts | 2 + .../services/heartbeat/session-preparation.ts | 15 +- .../heartbeat/workspace-preparation.test.ts | 224 +- .../heartbeat/workspace-preparation.ts | 162 +- .../src/services/heartbeat/workspaces.test.ts | 33 +- server/src/services/heartbeat/workspaces.ts | 385 +- server/src/services/issues.ts | 213 +- server/src/services/local-repository-path.ts | 13 + .../services/managed-repository-checkout.ts | 149 + .../native-chat-workspace.test.ts | 4 + .../native-runtime/native-chat-workspace.ts | 55 +- .../native-runtime/native-run-trace.test.ts | 28 +- .../native-runtime/native-run-trace.ts | 30 +- .../native-session-executor.test.ts | 6 +- .../native-runtime/native-session-executor.ts | 10 +- .../native-runtime/native-workspace-sync.ts | 101 +- .../paperclip-runner-tool-authority.test.ts | 5 +- .../paperclip-runner-tool-authority.ts | 13 +- .../src/services/project-default-workspace.ts | 23 + server/src/services/projects.ts | 48 +- server/src/services/runtime-service-env.ts | 14 + .../src/services/task-workspace-selection.ts | 9 + .../services/task-workspace-source-access.ts | 62 + server/src/services/task-workspace-tools.ts | 36 + server/src/services/trust-preset-resolver.ts | 12 +- .../src/services/workspace-file-resources.ts | 68 +- .../src/services/workspace-host-provision.ts | 32 + server/src/services/workspace-runtime.ts | 64 +- skills/paperclip/SKILL.md | 6 + .../paperclip/references/issue-workspaces.md | 16 +- tests/runner-e2e/FIXTURES.md | 18 + tests/runner-e2e/README.md | 42 + tests/runner-e2e/SECURITY.md | 11 + tests/runner-e2e/catalog.test.ts | 6 +- tests/runner-e2e/catalog.ts | 11 + tests/runner-e2e/live-fixtures.ts | 2 +- tests/runner-e2e/pi-controls-evidence.test.ts | 2 +- tests/runner-e2e/registered-artifact.test.ts | 25 +- tests/runner-e2e/registered-artifact.ts | 4 +- tests/runner-e2e/runner.spec.ts | 13 +- tests/runner-e2e/task-workspaces-cases.ts | 73 + tests/runner-e2e/task-workspaces-flow.ts | 182 + tests/runner-e2e/task-workspaces-scoring.ts | 83 + tests/runner-e2e/task-workspaces.test.ts | 92 + tests/runner-e2e/types.ts | 1 + ui/src/api/chatEndpoints.ts | 4 +- ui/src/api/execution-workspaces.test.ts | 7 + ui/src/api/execution-workspaces.ts | 2 + ui/src/components/IssueProperties.test.tsx | 31 + .../ProjectWorkspaceSummaryCard.tsx | 4 +- .../components/ProjectWorkspacesContent.tsx | 17 +- .../issue-properties/IssueProperties.tsx | 25 +- ui/src/lib/honeycomb-run-link.test.ts | 8 +- ui/src/lib/honeycomb-run-link.ts | 2 +- ui/src/pages/ExecutionWorkspaceDetail.tsx | 8 +- ui/src/pages/Workspaces.test.tsx | 12 + ui/src/pages/Workspaces.tsx | 16 +- ui/src/pages/apps/chat/ChatEndpointDetail.tsx | 33 +- .../apps/chat/ChatExecutionDefaults.test.tsx | 58 + .../pages/apps/chat/ChatExecutionDefaults.tsx | 106 + .../apps/chat/GitHubBotManagement.test.tsx | 77 + .../pages/apps/chat/GitHubBotManagement.tsx | 20 + .../chat-management.stories.tsx | 6 +- .../management.stories.tsx | 1 + .../fixture-api.test.tsx | 23 + .../github-bot-management/fixture-api.tsx | 30 +- 237 files changed, 191962 insertions(+), 1483 deletions(-) create mode 100644 doc/plans/2026-10-09-task-workspaces.md create mode 100644 packages/adapter-utils/src/sandbox-managed-deletions.test.ts create mode 100644 packages/adapter-utils/src/sandbox-managed-seed.test.ts create mode 100644 packages/adapter-utils/src/workspace-checkpoint.test.ts create mode 100644 packages/adapter-utils/src/workspace-checkpoint.ts create mode 100644 packages/adapter-utils/src/workspace-seed-cache.test.ts create mode 100644 packages/adapter-utils/src/workspace-seed-cache.ts create mode 100644 packages/db/src/migrations/0336_big_crystal.sql create mode 100644 packages/db/src/migrations/0337_exotic_proemial_gods.sql create mode 100644 packages/db/src/migrations/0338_absurd_vengeance.sql create mode 100644 packages/db/src/migrations/meta/0336_snapshot.json create mode 100644 packages/db/src/migrations/meta/0337_snapshot.json create mode 100644 packages/db/src/migrations/meta/0338_snapshot.json create mode 100644 packages/db/src/schema/execution_workspace_repositories.ts create mode 100644 packages/db/src/schema/execution_workspace_repository_access.ts create mode 100644 packages/db/src/schema/execution_workspace_repository_roots.ts create mode 100644 packages/paperclip-runner/src/protocol-actions/get-workspace.ts create mode 100644 packages/paperclip-runner/src/protocol-actions/list-workspaces.ts create mode 100644 packages/paperclip-runner/src/protocol-actions/prepare-repository.ts create mode 100644 packages/paperclip-runner/src/protocol-actions/select-workspace.ts create mode 100644 packages/shared/src/types/task-workspace.ts create mode 100644 packages/shared/src/validators/task-workspace.ts create mode 100644 packages/shared/src/validators/workspace-repository.ts create mode 100644 server/src/__tests__/chat-execution-defaults.test.ts create mode 100644 server/src/__tests__/heartbeat-workspace-source-trust.test.ts create mode 100644 server/src/__tests__/helpers/isolated-test-home.ts create mode 100644 server/src/__tests__/helpers/repository-trust-fixture.ts create mode 100644 server/src/__tests__/isolated-test-home.test.ts create mode 100644 server/src/__tests__/managed-repository-checkout.test.ts create mode 100644 server/src/__tests__/project-workspace-root-provenance.test.ts create mode 100644 server/src/__tests__/task-workspace-admission.test.ts create mode 100644 server/src/__tests__/task-workspace-api.test.ts create mode 100644 server/src/__tests__/task-workspace-binding.test.ts create mode 100644 server/src/__tests__/task-workspace-child-trust.test.ts create mode 100644 server/src/__tests__/task-workspace-repositories-persistence.test.ts create mode 100644 server/src/__tests__/task-workspace-repositories-trust.test.ts create mode 100644 server/src/__tests__/task-workspace-repositories.test.ts create mode 100644 server/src/__tests__/task-workspace-source-copy-privacy.test.ts create mode 100644 server/src/__tests__/task-workspace-tools.test.ts create mode 100644 server/src/__tests__/workspace-host-provision.test.ts create mode 100644 server/src/routes/task-workspaces.ts create mode 100644 server/src/services/chat-execution-defaults.ts create mode 100644 server/src/services/execution-workspace-repositories.ts create mode 100644 server/src/services/execution-workspace-source-roots.ts create mode 100644 server/src/services/local-repository-path.ts create mode 100644 server/src/services/managed-repository-checkout.ts create mode 100644 server/src/services/project-default-workspace.ts create mode 100644 server/src/services/runtime-service-env.ts create mode 100644 server/src/services/task-workspace-selection.ts create mode 100644 server/src/services/task-workspace-source-access.ts create mode 100644 server/src/services/task-workspace-tools.ts create mode 100644 server/src/services/workspace-host-provision.ts create mode 100644 tests/runner-e2e/task-workspaces-cases.ts create mode 100644 tests/runner-e2e/task-workspaces-flow.ts create mode 100644 tests/runner-e2e/task-workspaces-scoring.ts create mode 100644 tests/runner-e2e/task-workspaces.test.ts create mode 100644 ui/src/pages/apps/chat/ChatExecutionDefaults.test.tsx create mode 100644 ui/src/pages/apps/chat/ChatExecutionDefaults.tsx diff --git a/doc/DATABASE.md b/doc/DATABASE.md index 5862fe40a55..959658e603a 100644 --- a/doc/DATABASE.md +++ b/doc/DATABASE.md @@ -755,3 +755,48 @@ Use the same root client for in-process writers. New queue insertion paths must register before writing, or they can remain unseen until the next startup or another notification. Tests should verify both the producer and its outer transaction boundary. + + +### Task workspace records (2026-10-09) + +Migration [0336_big_crystal.sql](../packages/db/src/migrations/0336_big_crystal.sql) makes `execution_workspaces.project_id` nullable and changes its project reference to `ON DELETE RESTRICT`: source-project policy cannot disappear while a workspace still depends on it. `issues.execution_workspace_id` remains the sole active task binding. Versioned selection/pending-selection JSON and a binding revision record intent and protect transitions; they are not additional active bindings. Accepting a new pending choice advances the revision without changing the active folder. Its receipt retains the original request revision, so current-choice retries are idempotent and superseded retries fail the revision check. + +`execution_workspace_repositories` is company-scoped inventory beneath an execution workspace. It records repository identity, contained relative path, requested ref, pinned commit, preparation state and idempotency receipts. Credentials are resolved at use and are never stored in this inventory. Connection/destination `execution_defaults` JSON contains optional typed project/workspace choices, never arbitrary host paths or commands. + +Migration [0337_exotic_proemial_gods.sql](../packages/db/src/migrations/0337_exotic_proemial_gods.sql) adds internal `execution_workspace_repository_roots` records. A company and hash of its canonical physical path uniquely identify a sticky `anonymous_only` or `managed` acquisition reservation. Short transactions serialize reservations with repository requests and credential promotion, preventing private publication after low-trust admission checks. Records have no workspace foreign key: mutable workspace metadata, relocation, archival or deletion cannot release a physical folder still accessible to an admitted run. There is no automatic mode change or reservation deletion. + +Migration [0338_absurd_vengeance.sql](../packages/db/src/migrations/0338_absurd_vengeance.sql) adds internal `execution_workspace_repository_access` provenance. Company/canonical-root source entries retain execution-workspace and source-project identities; reader entries retain the exact admitted run, task, agent and responsible user. New repository receipts also record their root at creation; legacy receipts receive no inferred origin. Shared project-source entries retain which physical owners require workspace write authority. Current admission, selection, requests and publication check the current task’s writer against those owners; retained past readers and file APIs still require only read authority. Genuine isolated copies retain source read access without requiring shared-folder write access. An existing linked worktree can recover a missing isolated-copy proof only after Git registration and its captured source owner are verified. Configured local sibling copies retain deny-only task/project source ownership on the bound task root before materialization; ordinary repository clones create no isolation proof. The shared root fence checks every reader against every source before admission and future publication, including tasks without repository requests. File APIs apply the same source privacy without writing provenance. These entries grant no authority and have no workspace or run deletion cascade; unavailable retained identity fails closed. Automatic provenance release is deferred, so a newly incompatible permission scope requires a separate physical root. + +Git-source capture checks the actual common Git storage directory and verified +checkout owners. Separately stored metadata does not make its parent a checkout. +When a linked worktree cannot identify its primary checkout, the service verifies +company-known configured, execution, and retained roots against that metadata +identity. A primary checkout identifies itself when its Git directory equals its +common directory, including separate metadata layouts. Verified primary sources +and linked worktrees with a verified registered primary avoid the fallback scan. + +Writable selection, source-copy preparation, admission, and publication check the trust policy of +current and retained physical source projects. A projectless alias cannot bypass +a source's sandbox requirement. An incompatible preset or boundary is rejected; +the check never silently changes the selected execution environment. Admitted +and retained readers use their persisted effective run policy; absence of a +retained low-trust policy counts as standard. Live policy edits cannot relabel +a standard local run as sandboxed. Read-only file access +keeps its existing authorization rules. + +Source-copy checks retain deny-only ownership on failure but create a new +isolated-copy proof only after physical identity, access, and trust checks pass. +For low-trust sandbox runs, the harness also disables host worktree provisioning +on fresh realization, reuse, and rebuild. This covers configured commands and +the repository's built-in provision script; an empty configured command alone +would still allow that fallback. The capability comes from the server's selected +trust policy and environment, never repository configuration. + +After source authorization and retained-reader checks pass, the existing +`source` and `project_source` kinds retain ownership on shared Git storage. +Removing the original checkout cannot release private committed files in surviving +worktrees. These records grant no authority and create no isolation proof. Denied +source captures leave no new records. Worktree realization uses a verified checkout +as its Git command directory, including when metadata sits inside another repository. + +No migration scans or copies existing agent home directories. Keep admitted native descriptors and recovery references until compatible controllers finish their persistence obligations. See [the architecture rollout contract](plans/2026-10-09-task-workspaces.md). diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index ae303f2d9b2..0241771ebf5 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2276,6 +2276,14 @@ it needs no running Paperclip instance or provider credentials. The same spec li in the default `test:e2e` discovery tree, so the existing Chrome CI shards run it on pull requests. +### Workspace feature checks (2026-10-09) + +Task files, personal agent state (`AGENT_HOME`), and provider home are distinct. New filesystem tasks without an explicit source use a stable company/task directory. Debug workspace placement through the task workspace API rather than inferring it from organizational project or agent identity. + +Focused coverage lives in `task-workspace-binding.test.ts`, `task-workspace-repositories*.test.ts`, `task-workspace-tools.test.ts`, channel integration tests, and adapter-utils checkpoint/native persistence tests. Full handoff still requires the repository typecheck, test, and build commands. Remote transport fixtures establish protocol behavior; they do not qualify production provider throughput. + +Channel defaults can be configured in Apps connection settings and per destination. They affect new conversations only. A queued selection or repository request is applied on the next normal admission; configuration changes do not interrupt a live run. See [the architecture](plans/2026-10-09-task-workspaces.md). + ### Slack provisioning paths Self-hosted development continues to use independent Slack app setup. Managed diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 00d0ac8b5b5..29f20621639 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -2160,6 +2160,17 @@ Company Settings → General can configure one shared API-key connection for opt The internal service provides local-only availability and bounded, reauthorized execution. It records metadata-only invocation history and independent fractional service charges in the existing cost ledger, applying company and applicable agent/project budgets under the accounting lock. Unknown dispatch charges retain reservations until audited resolution. Settings/testing require connection management permission; history uses existing cost visibility. See [decision-models.md](decision-models.md) for the contract, supported models, accounting, privacy, and endpoints. + +### Task workspace ownership (2026-10-09) + +A task's organizational project and filesystem workspace are independent. New tasks that need a filesystem and have no selected/configured workspace get a company/task directory, not the agent home. Plain tasks need neither Git nor a project. Existing bindings and admitted roots remain authoritative; changing project does not move files. Explicit workspace selections apply at a subsequent normal admission after prior execution and persistence obligations settle. + +Chat connection and destination settings may supply project/workspace defaults. Destination fields override connection fields; omitted values inherit and null clears the inherited choice. Resolve and authorize these defaults once for a new conversation, then retain the resulting task intent. Existing conversations do not change when settings change. Trust confinement still takes precedence. + +Agents inspect/select workspaces and request contained repository preparation through ordinary authorized APIs. Repository preparation is idempotent and may require the next admission; it never claims a host-only clone is available in an already-running remote session. Simultaneous tasks may still explicitly share one physical local/SSH folder. Remote sharing requires actual placement and coherent persistence capability. + +See [the layer architecture](plans/2026-10-09-task-workspaces.md) for resolution rules, module ownership, persistence versions, rollout, and verification. + ## Company fast-response service Companies may configure an optional shared API connection and model for short acknowledgements of accepted human turns. Fast responses use direct SDK inference alongside normal agent work, with a three-second admission deadline, bounded prompts/output, current authorization and independent `fast_response` accounting. They are server-marked platform receipts attributed to the agent, never completion, interaction or recovery evidence. Company-sponsored unlinked external senders are explicit; linked-user permission denials are never bypassed. See [fast-responses.md](fast-responses.md) for provider, delivery, history and endpoint contracts. diff --git a/doc/SPEC.md b/doc/SPEC.md index 6df1dbb2337..c56c8fd8f47 100644 --- a/doc/SPEC.md +++ b/doc/SPEC.md @@ -766,6 +766,13 @@ authentication, replay, document-sized limits, inspection, and deletion semantic A company may configure a shared decision model for optional Paperclip features. The instance owns credential resolution, authorization, budget admission, and attributable service charges. Company-sponsored background use is enabled by default during configuration; explicit opt-out persists. User and agent requests keep their own access boundaries and cannot become sponsored background requests after denial. Availability is a cheap local capability check, and metadata-only request history makes service usage inspectable. The implemented V1 contract is in [decision-models.md](decision-models.md). + +### Task workspace ownership (2026-10-09) + +Organizational projects, task files, personal agent home, and provider home are separate concepts. App and chat tasks share one workspace policy: preserve a binding or explicit configured folder, otherwise allocate a stable task directory. A repository is optional and can be prepared without creating a project. Optional connection and destination defaults feed the same task policy; they do not grant permissions or relocate existing conversations. + +Agents select authorized workspace/repository intent while the harness owns paths, preparation, persistence and safe transitions. Intentional simultaneous sharing of one physical local/SSH folder remains supported. Provider sessions keep their admitted root through completion and recovery. See [SPEC-implementation.md](SPEC-implementation.md) and [the layer architecture](plans/2026-10-09-task-workspaces.md) for the versioned contracts and rollout constraints. + ### Fast responses A company may use a shared API model to acknowledge accepted human messages across tasks, agent chat and external channels while normal work starts. Acknowledgements retain their platform provenance and cannot satisfy execution or completion requirements. The bounded inference, authorization, sponsorship and independent accounting contract is defined in [fast-responses.md](fast-responses.md). diff --git a/doc/evals.md b/doc/evals.md index 97b17733496..6dd6e66ce00 100644 --- a/doc/evals.md +++ b/doc/evals.md @@ -452,3 +452,9 @@ See the [suite contract](../tests/runner-e2e/README.md#conversational-confirmati Hiring notification accounting now also requires exact completed action attribution. Missing native/provider ID mapping is uncomparable evidence; it must not be reported as a model task regression or waived through name/order matching. The fixture waits for both known completion callbacks and settled bracketed observations, including the gap before pending outbox work becomes a wake. Strict action replay and original machine verdicts are retained separately. The explicit-only [planning guidance utility comparison](../tests/runner-e2e/PLAN-TASK-GUIDANCE.md) measures task decomposition and handoffs with current, short, and disabled skills. + +The explicit-only [task-workspaces Product E2E suite](../tests/runner-e2e/README.md#projectless-task-workspaces-explicit-only) +covers projectless task files, deferred repository acquisition, controller restart +and nested Git/dirty-file persistence on legacy local, native local and native +Daytona OpenCode. Its three configured cells use independent filesystem, public +API and download oracles; no live qualification is implied by fixture registration. diff --git a/doc/observability.md b/doc/observability.md index 807ad61c80a..4a331900469 100644 --- a/doc/observability.md +++ b/doc/observability.md @@ -164,14 +164,19 @@ Those sections follow below. ## Native Runner Trace Spans Paperclip Runner task runs emit a single foldable OpenTelemetry trace. This is -the native-run trace schema version `2`. `task.run` is the only full-run root; +the native-run trace schema version `3`. `task.provider_session` is the native +provider lifecycle root; it includes preparation and provider execution, but +ends before outer workspace persistence. The heartbeat-owned local run-log +`task.run.measured` records terminal elapsed time including durable copyback; every other native span carries a real OpenTelemetry parent context rather than only a descriptive `parentName` field. +The run link in Honeycomb matches both `task.provider_session` for native runs and `task.run` for legacy adapters. It retains the exact hashed run-ID and service filters and groups by trace ID. The URL builder uses Honeycomb’s supported `in` operator for the two root names; see the [query specification](https://github.com/honeycombio/honeycomb-mcp#query-specification-for-run_query). This changes only the query link. The operator endpoint gate and span attribute allowlist remain unchanged. + The canonical lifecycle is: ```text -task.run +task.provider_session ├── heartbeat.queue ├── task.prepare │ ├── environment.startup @@ -253,10 +258,11 @@ and outcome. Native child attributes use the bounded `paperclip.native.span.` prefix and a closed key allowlist; values are limited to finite numbers, booleans, or short strings. Commands, arguments, environment values, paths, output, credentials, and raw identifiers are discarded by the -trace helper. `task.run.measured` remains in the local run log for compatibility -but is not exported as a second full-width OTel span. +trace helper. `task.provider_session.measured` records that same interval in the +local run log, without a duplicate full-width OTel span. The separate heartbeat +`task.run.measured` event is the authoritative end-to-end duration. Persisted `run.performance.span` events retain the v1 run-log schema and include -`traceSchemaVersion: 2` so local tooling can distinguish the hierarchy. +`traceSchemaVersion: 3` so local tooling can distinguish the hierarchy. Like every span in this document, native-run spans are opt-in. When `OTEL_EXPORTER_OTLP_ENDPOINT` is unset, the tracer remains a no-op; the local @@ -1054,16 +1060,16 @@ spans. A run-time (`sandbox.agentSession.sendInput`, `sandbox.agentSession.pollOutput`, `sandbox.callbackBridge.relayRequest`, or `sandbox.agentProcess`). Each run-time wrapper span parents to the live run span (`agent.turn` during the turn, -`task.run` otherwise). With no active trace context the exec span opens +`task.provider_session` otherwise). With no active trace context the exec span opens unparented. `sandbox.agentProcess` wraps the persistent streamed agent process. The process-session bridge launches it during `bridge.process-session`, so it opens -under `task.run` — no turn has started yet. It therefore overlaps the sibling +under `task.provider_session` — no turn has started yet. It therefore overlaps the sibling `agent.turn` rather than nesting under it or dangling off the short-lived bring-up step. The span ends when the process settles or when the bridge tears down, whichever comes first. The bridge tears down before the run root span ends, so -the span never outlives `task.run` even when the process lingers past teardown +the span never outlives `task.provider_session` even when the process lingers past teardown (the sandbox `execute` has no cancel, so a lingering process cannot be forced to resolve). diff --git a/doc/plans/2026-10-09-task-workspaces.md b/doc/plans/2026-10-09-task-workspaces.md new file mode 100644 index 00000000000..c12873ddc10 --- /dev/null +++ b/doc/plans/2026-10-09-task-workspaces.md @@ -0,0 +1,153 @@ +# Selected architecture — 2026-10-09 + +This design addresses PAP-171: a short agent turn paid minutes of workspace hydration and copyback because a projectless chat task inherited a large agent fallback directory. It covers app/API and chat tasks, including optional channel defaults, independent project and filesystem choices, explicit physical sharing, and deterministic harness preparation. Two architecture workers received the same full plan and grounding. Both candidates and an independent review used the inherited parent model; this is not cross-model consensus. + +## Caller contract first + +App/API/chat issue creation supplies typed workspace intent to the same workspace owner. Chat supplies optional defaults and authenticated source context, never paths or credentials. Task creation is transactional and cheap. Run preparation resolves an already-bound workspace or deterministically creates a company/task directory; provider, budget, run ownership and terminal disposition stay with heartbeat. The workspace owner does not become a second heartbeat orchestrator. + +Agent tools inspect workspace, discover existing authorized resources, prepare a repository, and explicitly select a workspace. Callers choose intent, never host shell commands or arbitrary destination paths. A repository checkout is optional and never requires a project. Root changes apply only at a safe subsequent admission; existing provider roots are immutable. Ordinary project edits do not move files. + +## Synthesis decision + +Base A retains issues.executionWorkspaceId as the single active binding and execution-workspace service as the mutation owner. Reject B's replacement binding/realization tables: existing environment leases and native descriptors already own physical and recovery facts. Graft B's narrow provider-facing context, durable settlement by run ID (reload accepted result/source evidence), explicit retention references, atomic repository subtree publication and project-deletion safety. Keep source-project authorization distinct from organizational task project. + +Parent scores: A 4/4/4/4/4; B 4/3/4/4/2 on ownership, preserved invariants, origin/tool parity, sync improvement, migration feasibility. Cross-judge independently chose A with the same scores. Neither candidate is implemented unchanged: their broad prepareRun/settleRun facade must not move unrelated execution orchestration into the workspace module. + +## State and boundaries + +- `issues.executionWorkspaceId`: canonical active binding. Workspace selection/revision and pending intent are typed/versioned task fields, not a second binding. Atomic issue lock/CAS and idempotent mutation receipts protect transitions. +- `execution_workspaces`: filesystem ownership/lifecycle and optional source project. A task-owned workspace has stable company/task identity, independent of agent. Existing rows/paths retain identity. Preserve historical privacy-source intersection. Project deletion cannot cascade needed workspace/recovery state; retain the source policy tombstone or reject deletion until explicit lifecycle cleanup. +- Repository inventory: one company-scoped child table for verified repository identity, relative path, pinned ref/branch, preparation operation/state. No plaintext credentials. Unique contained destination and source intent suppress concurrent duplicate preparation. Resolve symbolic/default ref once; another ref never silently switches a dirty checkout. +- Existing environment orchestration/leases: machine/provider placement and physical ownership. Workspace service asks it to realize selected intent. Reuse sharedWorkspaceKey/canonical paths; same logical ID does not imply same machine. Provider sessions and credentials remain agent/run scoped. +- Existing native sync/descriptors: admitted run baseline, exact source retention, immutable manifests/seeds and durable completion. Generic manifest/transport code stays in adapter-utils. AGENT_HOME retains its separate store and policy. +- Run lifetime and disposition remain owned by heartbeat. Workspace timings wrap actual filesystem work; provider turn is a child interval. Native admission ordering is unchanged: durable ownership/input before corresponding external effects, never reconstruct an admitted root from fresh defaults. +- Routes and runner authority translate/authenticate and call the domain owner. Share validators and generate tool descriptors from the canonical protocol action definitions. No parallel tool-only policy implementation. + +## Resolution and transition rules + +1. Already-admitted run: use immutable recorded input/recovery evidence. +2. New admission: eligible pending explicit selection, only after prior process and persistence obligations settle. +3. Otherwise current durable binding. +4. Unbound task: explicit selection > eligible parent inheritance > snapshotted resource/endpoint defaults > configured project policy > explicit configured adapter cwd > task directory. + +Explicit invalid/unauthorized choices fail with actionable errors, never fall back to home. Existing session cwd becomes a compatibility binding only with verified provenance. No eager scan/copy/migration of legacy homes. Workspace-free providers remain workspace-free. Existing low-trust/native-chat confinement stays authoritative and cannot be relaxed by defaults. + +Typed selection vocabulary: task_directory; existing execution workspace; configured project source with shared or managed-isolated policy. Operator cwd compatibility is internal and cannot be submitted by an ordinary agent. New repository paths use existing `.paperclip-repositories/` namespace; existing root checkouts retain their paths. + +Source inspection and isolated copies require source-project read access. Writable shared roots additionally require the internal `project:write_workspace` authorization action. It reuses existing `tasks:assign` and `tasks:assign_scope` grants for the source project and actual assignee, but evaluates only the source project’s assignment policy: continuing a task does not reassign its protected task, parent, or agent. Task lineage still enforces low-trust containment, and the responsible-user intersection applies independently. The resolved strategy determines sharing: only `git_worktree` creates a separate checkout; an isolated mode label does not exempt a source-root strategy. Board requests use the task's actual assignee when checking scoped grants. Check that authority at selection, task creation, repository mutation, and each new admission; revocation blocks future use without changing already-admitted recovery inputs. + +Independent repository acquisition does not inherit private-repository authority from a chat endpoint's sponsor. Effective low-trust tasks cannot request new controller-backed repository acquisition, including anonymous public URLs; provider full-auto permissions do not override this capability boundary. Pending or preparing intents fail before Git after trust is downgraded. Already-ready public checkouts may be reused only after validating their confined directory, persisted pin and Git origin without cloning, repair or lazy object fetch; managed receipts remain forbidden. Resolve current agent, task, organizational project and workspace-source policies together with the retained run policy at request and admission. Existing configured-source authorization remains the path for private sources. A trust change must fail before cloning or reusing existing private bytes. Empty-inventory admission, authorized file/discovery reads and already-admitted immutable recovery remain available. Trusted routine and local-board acquisitions retain their existing authority contract; agent requests still require the persisted run responsible identity. Trusted controller acquisition has no per-repository byte or cumulative storage quota in this feature, so trusted full-auto acquisition remains an operator resource risk; a general storage quota system is deferred. + +Admission with no repository requests remains heartbeat-owned and requires no additional repository-mutation permission. Low-trust admission still rejects managed acquisition bytes in the same physical folder. Immutable recovery uses its admitted folder and skips new repository acquisition even if the task's current binding has changed. A reservation-only recovery hook fences legacy admitted host roots before input capture or provider dispatch. Local recovery uses its immutable local root; sandbox recovery obtains the host path from its verified immutable sync descriptor. SSH recovery uses its remote filesystem directly and does not require a host snapshot descriptor. Older projectless native runs may use the exact persisted run-ID workspace binding without a workspace row. + +Task creation supplies an explicit internal prospective-task context to workspace selection. An admitted agent choosing an existing workspace for a child must have an authoritative saved run, a matching company and agent identity, and a verified parent lineage. The server checks current parent and prospective child policies against the saved run boundary and the physical source policy before saving the child. The creator run belongs to its existing task, not the unsaved child. Ordinary existing-task selection and writes still require their exact saved task/run identity; a parent ID alone does not enable the creation path. + +Independent acquisition reserves a company-scoped canonical physical root as either `anonymous_only` or `managed` in an internal durable record. A short database fence serializes these reservations with repository requests and credential promotion. Low-trust use rejects every managed intent, including a clone still in staging, and reserves anonymous use before capture. Future managed requests cannot publish into that reserved root. The reservation survives controller restart, workspace aliases, metadata replacement, relocation and archival; changing the acquisition mode requires a separate physical root. No database lock remains held through cloning, input capture or provider execution. Anonymous admission also checks the readable candidate repository namespace against company-managed receipt identities, including on an existing reservation. A published identity collision is denied even when the old receipt path is unreadable; mutable ownership markers cannot authorize it. This conservatively requires a fresh root when anonymous and managed publications share an identity. Reservations also compare retained overlapping parent/child roots; component-boundary siblings remain independent. Initial reservations compare legacy managed intents through accessible canonical ancestors, conservatively rejecting overlapping inaccessible paths while unavailable unrelated historical folders do not block admission. Arbitrary external file writers remain subject to the existing shared-folder constraints. + +Shared physical roots also retain source workspace and admitted reader identities separately from acquisition mode. Empty inventories do not bypass peer privacy: admission and recovery record the reader; requests and publication must remain readable to every retained reader. Admission registers the root before preparing configured sibling repositories. Each local sibling source is authorized before materialization, including local file URLs without a configured cwd and reuse of an existing sibling checkout. File-URL-only sources keep ordinary clone semantics; their source permissions still follow the copied files. Its captured task and project owners are retained on the bound task root before copying, so staging, published and detached files keep source privacy. A publication callback rechecks source permissions and every retained destination reader. Ordinary sibling clones add only read restrictions; they never create a worktree-isolation proof. The fence uses authoritative workspace/run/task identities and current source privacy, never writable Git ownership markers. Ordinary heartbeat-owned admission to the same task's sole source adds no repository-mutation requirement, while any peer source or task requires the full read intersection. Missing retained identity fails closed; automatic release is deferred. Current writers must also satisfy `project:write_workspace` for every shared physical source project, including configured aliases with no execution row. Sharing classification is retained separately from read provenance and survives pointer changes. Selection checks without registering a reader; admission and publication independently recheck current writer authority. Proven isolated worktree copies require source read authority; past readers never receive an added mutation requirement. + +Repository identity alone does not prove that a workspace published into a particular folder. Available unrelated copies remain independent; retained sources and ambiguous unavailable legacy publishers still constrain access. Physical-source authority includes canonical ancestor and descendant overlaps, current configured/execution roots, and retained source/readers. Every admission/publication rechecks current roots even after a reader has been recorded. Configured workspace move/removal and project removal preserve source provenance before their first admission too. Valid local file URLs and absolute repository paths also identify physical configured owners when no usable cwd exists. The same source resolver protects copying, file reads and configuration detachment. File listing, previews and downloads use the same read-only physical-source resolution. Those reads create no policy or reader records. Git worktree preparation checks actual checkout and common-repository source roots before copying. Server-owned captured source identities are retained on the isolated target before Git/provisioning and rechecked before exposure. The isolated_source and isolated_project_source text kinds preserve task/project privacy before any copying. They are deny-only intent, not proof of isolation. The separate isolated_copy proof is recorded only after Git validates a registered linked worktree; the ancestor-source write exemption also requires the target to remain a real worktree. Preparation may temporarily allow a validated missing/empty target, without persisting that exemption. An intact existing linked worktree without a proof record is first verified against the captured source and Git registration. Only that verification can establish its proof and let it resume after an upgrade or interrupted preparation; occupied ordinary folders remain strict. Existing ordinary folders and exact/descendant target owners keep their write requirements. Deny-only copy provenance survives permission revocation or source deletion during preparation, including failed copies. Managed local sources used by main or referenced projects receive the same checks and committed staging ownership before Git reads bytes. Closed-worktree reopening commits source ownership before its lifecycle transaction, uses read-only checks under that lock, and retains completed-copy evidence after releasing it. Source and destination identities must remain unchanged; no company root fence is acquired while the lifecycle lock is held. Explicit child source choices, including clearing inherited linkage with null, take precedence over parent workspace inheritance; omitted choices retain inheritance and normal project defaults. + +Reopening has no selected execution lease. Before rebuilding a worktree, resolve the bound task’s current task, assignee, organizational-project and filesystem-source policies inside the lifecycle transaction. Any effective low-trust policy suppresses both configured and built-in host provision scripts. Missing referenced authority or denied policy blocks reopening; trusted provisioning keeps its existing behavior. A board comment does not authorize sandbox repository scripts to execute on the host. + +Admission derives controller provisioning from the authoritative selected environment, independently of task trust and filesystem permissions. Sandbox and SSH selections suppress configured and built-in controller provision scripts during creation, reuse and rebuild, including host runtime provisioning. Read-only source copying remains available for genuine isolated worktrees; source write grants and board sponsorship do not authorize controller execution. Selected isolated sources are copy-only for controller provisioning even when execution is local: explicit, channel, inherited and legacy choices do not record host-script approval. Foreign organizational/source project lineage and nondefault legacy sources also remain copy-only. Persist a deny-only marker on the realized workspace and preserve it through metadata updates, binding changes, reuse, rebuild and reopening; missing source authority must not become a default approval. Existing implicit trusted local project defaults and taskless operator runs with no source retain their behavior. One shared resolver gives task creation, bulk creation and controller classification the same configured-policy pointer, primary, creation-time and ID precedence. Creation keeps its existing source validation; classification separately requires the actual source to belong to the organizational project and company. A dangling configured pointer does not fall back. This correction does not rotate existing bindings or clear retained denial markers. Unknown task-backed or projectless source identity stays copy-only; the reserved operator-cwd provenance compatibility branch does not imply a new producer or approval API. SSH retains its explicit remote provision-command path. Sandbox workspace staging does not add a remote provision-command or runtime-provision transport in this change. + +Channel defaults use optional properties (inherit), explicit null (clear), concrete value (override). Resource override beats endpoint. Defaults are captured once at task creation and reauthorized on use. Changing/deleting/revoking configuration cannot relocate existing tasks. Project choice and workspace choice are independent; choosing a source does not silently assign organizational project. Endpoints without configuration work normally. + +The organizational task project retains task identity, environment, and secret scope regardless of the filesystem source. An organizational project with no usable configured folder or repository is not a filesystem source. Unbound tasks without typed selection still retain that project's explicit workspace requirements; persisted and typed source choices retain precedence. Both task-project and source-project policies constrain run trust; source policy controls workspace realization. Channel task creation separately checks assignment authority for the effective organizational project and actual assignee against locked endpoint values inside its transaction. Saving defaults does not grant future senders that authority. + +New native chat tasks may realize an authorized managed-isolated Git worktree before their first binding exists. The resulting durable task-owned worktree must pass the existing chat-root guard before provider dispatch; shared roots and mismatched admitted recovery roots remain rejected. + +Active root selection stores pending intent and returns next-normal-admission, without interrupting or scheduling a new model turn. If atomic safe nested acquisition/capture is supported, repository preparation may add a new contained subtree to the active root; otherwise it records preparation for next admission with explicit capability result. Never claim a host-only checkout is available in the running remote environment. + +## Sharing and durability + +Preserve simultaneous local/SSH tasks in an explicitly configured identical physical folder, including current local/SSH handling of legacy serialize settings. Administrative Git/checkout/restore actions retain target-scoped locks; ordinary provider work is concurrent. Closing one task cannot remove configured shared files or release another participant's resource. + +Concurrent remote sharing requires proven common physical placement, separate session authority and coherent snapshot capability. Unsupported new concurrent sharing returns a typed unavailable result; do not instantiate separate clones and label them shared. Already-admitted descriptors remain recoverable unchanged. Local authoritative roots need no replica copyback; SSH retains its actual existing persistence contract. + +Sparse filesystem checkpoints use disk-backed full manifests, changed bytes/deletions, safe-link and existing ignore semantics, and separate nested Git history. Capture still enumerates and hashes files with stable-read validation; a metadata hash cache is deferred. No-change sends zero file-content payload where capability is supported; manifest metadata still transfers; unsupported transport uses explicit confined full fallback. Sparse capture capacity failures before outbound data is read also use the existing confined full transfer, so a tiny output can recover when the sandbox cannot preserve the sparse manifest reserve. Returned manifest, path, digest and payload validation failures remain errors and never trigger fallback. Full checkpoint exports validate the completed staging root before merge, reject unsupported entries, and omit absolute or escaping links without following them; safe relative links remain supported. Reuse immutable durable seed generations, not only warm sandbox contents; reuse still verifies archive digests. Existing run references pin seeds/deltas until all admitted runs/recovery obligations release them. Retain the final host rescan because the run ownership fence does not exclude external writers to shared folders; do not reuse a stale post-merge manifest. + +A returned sparse snapshot must describe a coherent tree before any host mutation. Every nested entry requires an immediate parent marked as a directory in that same returned snapshot, including baseline-unchanged files. Checking every directory also verifies recursive ancestry. Top-level entries use the implicit root. Invalid trees fail without overwriting retained host files and keep recovery evidence. The check uses the existing indexed manifest interface; it adds no host-path scans or unbounded in-memory inventory. + +Seed recovery deliberately uses **no delta chain** in this implementation. An unchanged generation reuses its verified content-addressed immutable full seed; a changed generation creates a fresh full seed. Sparse output transfer is separate from seed recovery. This revises the sketch's 16-delta compaction proposal: composing recovery generations would add a new failure surface before the simpler cache has been qualified. Existing references retain old seeds; no automatic eviction/deletion policy is introduced. Optional cache publication is capped at 16 generations/1 GiB per workspace and 256 generations/256 workspace directories/8 GiB per company. A company-scoped filesystem lock serializes quota checks and publication across workspace roots. Archive and receipt bytes, corrupt entries and abandoned pending generations count against capacity; publication also preserves the manifest storage free-space reserve (256 MiB by default), accounting for a full-copy fallback. Saturation or cache failure skips publication and keeps the already-written per-run recovery seed. Existing over-capacity caches stop growing without deleting admitted evidence. This deliberately trades cache hit rate after saturation for bounded additional storage and unchanged recovery semantics; no global cross-company quota is added. Changed input generations still pay full snapshot creation. New cache entries are reconstructed and checked against the admitted baseline before publication, so concurrent writes cannot label an archive as the wrong generation; this adds cold verification work. A corrupted cache entry remains a miss and falls back to a fresh seed; automatic cache repair and garbage collection are deferred. The existing Git seed contract captures the selected branch/HEAD, not unrelated refs. + +Ordinary settlement remains host-durable before successful completion. Preserve export-only recovery and exact-source lease retention. Preserve the specific unsafe-export omission exception and its run-log-only diagnostic. Do not change global merge conflict semantics as a side effect. + +Folder-change guards track live run ownership, finalization work and pending file recovery. A historical stopped or declined run whose cleanup has settled must not permanently block the task's next folder choice. Retained export recovery and unresolved ownership still block changes until their durable release or persistence receipts exist. + +## Delivery and ownership + +The core implementation owns execution-workspace/task binding schema + shared selection contracts, workspace domain/resolver, issues/heartbeat integration and nullable-project read models. The persistence implementation owns adapter-utils/native sync/trace helpers, exposes integration hooks to core rather than concurrent heartbeat editing. The repository implementation owns acquisition service, inventory schema and routes/runner catalogs, coordinating domain interfaces with core. The integration layer owns channel defaults/config API/UI, documentation, migration generation and final integration/verification. All workers are in the same checkout; do not revert others. + +Generate the feature migrations centrally from finalized schemas, preserving the upstream journal prefix and snapshot chain. Task intent/inventory, physical acquisition policy and retained source access use sequential additive migrations. No handwritten snapshots. Upgrade is additive; old immutable inputs and descriptors remain readable. New selections/checkpoint versions must not be admitted by old controllers: deploy schema first, drain only local test processes if needed, activate new admissions after all serving controllers support them. Rolling back application must disable new admissions, retain new state and finish/recover already-admitted work on compatible controllers; no destructive down migration. + +## Acceptance + +Targeted tests cover app/chat parity, task directory without Git, reassignment, organization change without relocation, explicit configured-folder concurrency/lifecycle, privacy intersection, repository permission/idempotency/ref conflicts, pending root changes without interruption, accepted-result recovery, unsafe-export exception, old descriptors, and no access to unrelated home contents. Persistence fixtures prove unchanged zero payload and one-file/deletion changes where supported, seed reuse and corrupted/stale fallback. Timings include delayed finalization. UI settings/errors/projectless links work with token-only styling. Run full typecheck/test:run/build before handoff; distinguish fixture proof from unperformed live provider qualification. + +## Phase status + +- Ground: complete. +- Sketch: complete, A selected with stated grafts. +- Agree: user explicitly chose architecture then implementation; no additional checkpoint. +- Implement: code complete; targeted checks pass, broad-suite qualification remains incomplete (see verification record). +- Scrap: conditional; two repeated boundary workarounds require redesign rather than silent escape hatches. + + +## Layer plan and public usage + +| Layer | Responsibility | Contract | +| --- | --- | --- | +| Shared types and database | Durable task intent, one active binding, optional source project, repository inventory | `TaskWorkspaceSelection`; existing `issues.executionWorkspaceId` | +| Workspace domain | Authorize selection, queue safe transitions, bind stable task folder, retain source privacy | `validateSelection`, `inspectTaskWorkspace`, `selectTaskWorkspace`, `applyPendingTaskWorkspaceSelection` | +| Task creation | Resolve explicit/parent/channel intent without making filesystem side effects | `workspaceSelection` on create; source marked internally | +| Channels | Merge connection/destination defaults, check sender and agent access, snapshot once | `executionDefaults: { projectId?, workspace? }` | +| Repository preparation | Validate repository/ref, authorize credentials, publish contained checkout idempotently | Repository request queues for next normal admission | +| Runner tools | Discover authorized choices and submit intent | Get/list/select workspace; prepare repository | +| Harness | Determine cwd, prepare queued repositories before baseline, enforce trust/placement policy | Existing admission and environment ownership fences | +| Filesystem persistence | Stable manifests, sparse output, verified seed reuse, durable settlement | Backward-readable native descriptors; new descriptor version for nested repositories | +| UI | Optional defaults and projectless workspace navigation | Existing Apps settings and Workspaces surfaces | + +Example task creation: + +```ts +await issues.create(companyId, { + title: "Write the report", + projectId: null, + workspaceSelection: { kind: "task_directory" }, +}); +``` + +A task can later select an authorized existing workspace with its current binding revision and an idempotency key. Accepting a new pending selection atomically advances that revision, so retrying a superseded choice cannot replace a newer one. Retrying the current pending or applied request remains idempotent. The response states that the selection applies on the next normal admission; it does not restart the current process. A repository request similarly records preparation intent when the active provider root cannot safely accept a new checkout. Project assignment alone never requests this transition. + +Example channel defaults: + +```json +{ + "executionDefaults": { + "projectId": null, + "workspace": { "kind": "task_directory" } + } +} +``` + +A destination may override either field independently. Omitted fields inherit the connection value; `null` clears that field's inherited choice. A cleared workspace choice allows the ordinary project/task resolution path. Changing defaults affects new conversations only. Saving a destination default does not overwrite its independently managed enabled state. + +## Verification and rollout limits + +Tests must distinguish local fixture proof from live provider qualification. The implementation adds database and protocol tests, compatibility tests for old descriptors, plain-directory and nested-repository persistence fixtures, and sparse/checkpoint/cache tests. A passing fixture does not prove Daytona or SSH production throughput. The original diagnostic run is never interrupted or modified as part of this work. + +Deploy schema before admitting the new state. Native descriptor v3 requires compatible recovery controllers; mixed-version controllers must not claim new admissions. Rollback retains the schema and immutable seeds and routes existing v3 obligations to compatible controllers. Do not delete legacy home content, existing workspace directories, or accepted-result recovery evidence. + +### Local verification record — 2026-10-09 + +- Full `pnpm -r typecheck` and `pnpm build` passed. A final direct server `tsc --noEmit` passed after the last server corrections. The full build preceded those final corrections; it was not repeated. +- Generated runner contracts, migration validation, module boundaries, token gates, and `git diff --check` passed. +- Focused checks passed for workspace binding/admission/reopen, concurrent local/SSH sharing, task-folder isolation, channel defaults and email admission, projectless UI, and immutable recovery behavior. Final repository lifecycle batch: 19/19; file browsing: 38/38; sparse checkpoints/cache: 7/7; Git/history: 69/69; tracing: 11/11; executor timing: 2/2. +- `pnpm test:run` was attempted and stopped with exit 130 after roughly an hour of serial server tests and repeated fixture timeouts. Its observed failures were 54 chat cases, one executor timing case, and 23 runtime-service cases. This is **not** a passing full-suite result. The run also began before the final source edits, so it does not qualify the frozen final tree. +- A fresh retry passed 53/54 previously failing chat cases; the remaining Slack admission case exceeded its short asynchronous observation window. After one database-setup timeout, the final isolated retry passed with the original assertions and a larger setup-hook budget (1 passed, 1,145 skipped; `chat-slash-final.log`). Thus all 54 cases passed across fresh retries, not in one clean full run. The executor timing cases both pass in fresh processes. Slow local listener lookups were independently measured at 3.5–6 seconds each; do not interpret the broader runtime failures as proven product regressions or proven baseline failures without further isolation. +- No live SSH/Daytona performance qualification or staging deployment was performed. Clean full-suite qualification on the frozen tree remains a release prerequisite. + +Detailed local logs are retained in `/tmp/paperclip-workspace-architect/` (`build.log`, `typecheck-final.log`, `tests.log`, `chat-failures-retry.log`, `repository-lifecycle-tests.log`, and `file-resources-final.log`). Worker verification notes are in `core-progress.md`, `repository-progress.md`, and `sync-progress.md` there. diff --git a/doc/run-log-events.md b/doc/run-log-events.md index 9641c092e72..7c9080d96c6 100644 --- a/doc/run-log-events.md +++ b/doc/run-log-events.md @@ -217,6 +217,41 @@ The payload never carries a command, an argument, a path, an environment value, or a raw identifier. The event rides the `ctx.onEvent` run-event bridge and is run-log-only. It needs no OTLP endpoint. +## Workspace Restore and Settled Run Measurements + +The heartbeat controller writes these records with `eventType: "lifecycle"`, +`stream: "system"`, and `level: "info"`. The event name below is its `message` +and its payload's `phase`. They remain in the instance run log and add no +Paperclip Telemetry or OpenTelemetry export. + +| Event | Payload fields besides `phase` | Meaning | +| --- | --- | --- | +| `workspace_checkpoint` | `mode`, `scannedEntries`, `changedFiles`, `payloadBytes` | Metrics for a validated sparse checkpoint, or a full restore fallback. | +| `workspace.restore` | `durationMs` | Wall time around native workspace restoration. | +| `task.run.measured` | `durationMs`, `outcome`, `durableSettlement` | Wall time through terminal owner cleanup, with the persisted terminal run status and `durableSettlement: true`. | + +For `workspace_checkpoint`, `mode` is `"sparse"` or `"full"`. Sparse metrics +count snapshot entries in `scannedEntries`, regular files in the validated +downloaded payload in `changedFiles`, and their total bytes in `payloadBytes`. +Deleted entries and symlinks do not add to the regular-file count or byte total. +Full fallback reports all three counts as `null`: unavailable, not zero. A run +can emit multiple checkpoint records while restoring its repositories. These +payloads contain no paths, filenames, file contents, or credentials. + +`workspace.restore` is emitted when the timed restoration exits, including an +exception. It has no outcome field and does not by itself prove restore success. +Its producer is the native workspace sync `onPhase` callback. + +`task.run.measured` is emitted only after the controller observes a terminal run +with no remaining native runner ownership. Its duration starts at `startedAt`, +or `createdAt` if the run never started, and includes preparation, execution, +finalization, and cleanup. It is not model-only time; the creation-time fallback +also includes time spent queued. `outcome` is the actual terminal status, such as +`succeeded`, `failed`, or `cancelled`. Provider turn completion can precede this +settlement record. The record does not imply exactly-once emission across +controller recovery, and failure to write it is logged without changing the +already-settled outcome. + ## ACP terminal failure diagnostics The shared ACP adapter engine preserves typed terminal session failures in the diff --git a/maps/agent-access-map/chat-channels.md b/maps/agent-access-map/chat-channels.md index 4aeeccdcd8a..70ad018cb28 100644 --- a/maps/agent-access-map/chat-channels.md +++ b/maps/agent-access-map/chat-channels.md @@ -81,6 +81,13 @@ remaining operations before qualifying the whole capability. Gaps: Normal agent replies work through server delivery. Explicit endpoint publication APIs are board management paths. +New conversations resolve typed project/workspace defaults from their endpoint +and destination under the task-creation transaction. Defaults do not authorize +private sources: admission checks the linked user's authority and assigned agent. +An existing conversation keeps its task binding. See +[creation service](../../server/src/services/chat-channels.ts) and +[defaults contract](../../packages/shared/src/types/chat-channels.ts). + ## `attachments` Review: `baseline`. @@ -115,6 +122,7 @@ API: `present` — representative operation IDs: - `GET /api/chat-endpoints/{endpointId}/activity`. - `PATCH /api/chat-endpoints/{endpointId}`. +- `PUT /api/chat-endpoints/{endpointId}/resources` configures destination defaults. Agent access: `restricted` — Board lifecycle APIs; agent requests repair when blocked. @@ -130,3 +138,7 @@ own policy and access review. Gaps: Activity/repair/replay/disable use board endpoint access, not the agent's ability to reply. + +Endpoint/destination execution defaults use the same board-only management +boundary. Agents select an authorized workspace for their current task through +the [workspace APIs](workspaces.md), rather than editing channel configuration. diff --git a/maps/agent-access-map/workspaces.md b/maps/agent-access-map/workspaces.md index 5804821580c..8cbd031ab22 100644 --- a/maps/agent-access-map/workspaces.md +++ b/maps/agent-access-map/workspaces.md @@ -17,12 +17,50 @@ API: `present` — representative operation IDs: - `GET /api/issues/{id}/heartbeat-context`. - `GET /api/execution-workspaces/{id}`. +- `GET /api/issues/{id}/workspace` inspects the durable task binding. +- `PUT /api/issues/{id}/workspace` requests the next binding. +- `GET /api/companies/{companyId}/execution-workspaces?summary=true&selectableForTask=true` + lists authorized task-selectable workspaces. Agent access: `available` — Native get_task_context / REST / CLI / MCP workspace read. +The task-bound native tools `get_workspace`, `list_workspaces`, and +`select_workspace` use the ordinary authenticated APIs. Legacy agents can call +those APIs with their admitted run token. These paths do not require a project. + Policy: Company/project/workspace visibility and runtime authority; active run, trust preset and task ownership where required. +Task workspace writes require `issue:mutate`, an active owned task/run for agents, +and current source visibility. Shared physical roots also require source-project +workspace write authority. Accepting a new pending choice advances its binding +revision; superseded retries fail while current-choice retries remain idempotent. +Binding revisions and request keys fence retries; +selection applies at the next normal admission without interrupting a live run. +Writable aliases must also be compatible with current and retained physical +source projects' trust boundaries. Incompatible choices are rejected before +dispatch; a projectless alias cannot bypass required sandbox containment. +Source-copy checks apply the same trust boundary before provisioning. The +authoritative sandbox or SSH selection suppresses configured and built-in +controller worktree and runtime provisioning regardless of task trust, source +write grants or board sponsorship. Genuine isolated worktrees still permit +authorized read-only source copying. Source write permission does not authorize +controller execution. Explicit, channel, inherited, foreign or nondefault isolated +source choices remain copy-only for controller provisioning even on local runs. +Their deny-only workspace marker survives metadata and binding changes; a board +reopen does not turn it into approval. Implicit trusted local defaults and explicit +SSH remote provisioning retain their existing placement; no new sandbox provision transport +is claimed. The implicit default follows task creation: configured policy pointer, +then primary source, creation time and ID. A missing or foreign configured source +does not authorize controller hooks. This precedence neither changes existing +creation validation nor rotates bindings or clears retained copy-only markers. +Closed-worktree reopening checks current task, agent, organizational and source policies without assuming a future driver. Effective low-trust reopens suppress configured and built-in host scripts. Returned checkpoints must have directory parents for every nested entry before merging, including unchanged entries; invalid output leaves retained host files intact and keeps recovery evidence. +Child creation by an admitted agent checks an explicit prospective-task context before the child is saved. The creator run must have an authoritative saved policy and a verified company/agent/parent lineage. Current parent, prospective child and physical source policies must fit that boundary. This creation path does not relax exact task/run checks for existing-task writes. The [child trust regression](../../server/src/__tests__/task-workspace-child-trust.test.ts) is automated source-level evidence, not live agent qualification. +Source review for these additions: PR #15749 and +[task workspace routes](../../server/src/routes/task-workspaces.ts), +[native authority](../../server/src/services/native-runtime/paperclip-runner-tool-authority.ts), +and [semantic transport](../../server/src/services/task-workspace-tools.ts). + Evidence: `source-only` — [implementation](../../server/src/routes/workspace-runtime-service-authz.ts#L76) at the reviewed commit above. @@ -43,10 +81,21 @@ API: `present` — representative operation IDs: - `GET /api/execution-workspaces/{id}`. - `GET /api/execution-workspaces/{id}/workspace-operations`. +- `POST /api/issues/{id}/workspace/repositories` requests repository preparation. Agent access: `partial` — REST / native call_api reads; task/project assignment triggers server provisioning. +Native `prepare_repository` and admitted legacy REST callers can request a +catalog repository or validated repository URL for their current task. +Preparation occurs at normal admission; the agent cannot supply a host path or +controller identity. Current source permissions, responsible-user intersection, +trust restrictions, immutable receipts, and retained physical-root ownership are +checked before acquisition and publication. Low-trust callers cannot trigger a +new controller-side Git acquisition. Evidence remains source-only here; focused +[repository tests](../../server/src/__tests__/task-workspace-repositories-trust.test.ts) +are separate from live agent qualification. + Policy: Company/project/workspace visibility and runtime authority; active run, trust preset and task ownership where required. @@ -115,6 +164,15 @@ remaining operations before qualifying the whole capability. Gaps: Files panel HTTP requires board. Agents can use their admitted workspace file tools; remote/Dot scope and Git command availability differ. +File reads also enforce retained source privacy after local copy, configuration +move/removal, reopen, and project deletion. A task assignment or repository +receipt does not grant access to private files from another physical owner. + +Authorized Git-source capture also retains ownership on the actual shared Git +storage. Source checks use verified checkout identities, including company-known +owners when separate metadata has no primary-checkout registration. Removing that +checkout does not release its private committed files in surviving worktrees. + ## `closure` Review: `baseline`. diff --git a/maps/feature-map/chat-channels.md b/maps/feature-map/chat-channels.md index 4a6a55efc2f..a5437be5639 100644 --- a/maps/feature-map/chat-channels.md +++ b/maps/feature-map/chat-channels.md @@ -44,6 +44,11 @@ Automated: [channel integration](../../server/src/__tests__/chat-channels.integr Manual: Inspect the bound agent and identity, recent activity, and delivery failure details. Repair a disposable credential, retry a safe message when offered, and verify exactly one external reply. Disable the endpoint and confirm new traffic is not silently processed. +Configure optional project and workspace defaults at the connection and destination +levels. Start a new conversation and inspect its task workspace. Verify that +inheritance, an explicit selection, and clearing a default remain distinct. +Changing defaults must not move an existing conversation or interrupt its run. + ## Gotchas - Visibility flags do not pause existing delivery. Disable the disposable diff --git a/maps/feature-map/workspaces.md b/maps/feature-map/workspaces.md index 786f5f4d8ef..c5bba194573 100644 --- a/maps/feature-map/workspaces.md +++ b/maps/feature-map/workspaces.md @@ -34,6 +34,41 @@ Automated: [workspace card](../../ui/src/components/IssueWorkspaceCard.test.tsx) Manual: Run a task that writes a small file, follow the bound workspace, and inspect the file and revision. Reload and verify links still refer to the same workspace, not another checkout or a stale run. +For task workspace selection, use the authorized task workspace API to choose a +task directory, project source, or existing workspace. Verify that a change +made during a run stays pending until the next normal admission. Submit another +choice using the returned revision, then retry the superseded request; it must +fail without replacing the newer pending choice. A projectless +task must retain its files across resume and reassignment. Use the repository +preparation API to add an authorized repository beneath that workspace; +repeat the request and verify that it reuses the checkout and pinned revision. +See [task binding tests](../../server/src/__tests__/task-workspace-binding.test.ts) +and [repository access tests](../../server/src/__tests__/task-workspace-repositories-trust.test.ts). +An existing projectless folder alias of a sandbox-restricted project must be +rejected for a standard local task before its adapter executes. Repeat after +relocating the configured source; retained ownership must still enforce the +boundary. This is separate from ordinary authorized file preview/download. +For a registered worktree selected for a sandbox or SSH environment, configured +and built-in provision scripts must not run on the controller during fresh +creation, reuse, or rebuild, including host runtime provisioning. Repeat with +standard trust and a protected read-only source, then with source write grants; +permissions must not change execution placement. Verify isolated source copying +and selected-environment dispatch still work. Repeat an explicit local isolated +source choice: copying remains available but controller scripts must stay absent, +including after binding or metadata changes and closed-worktree reopening. An +implicit trusted local project default remains a positive provisioning control. +Repeat with a newer configured default overriding an older primary source, then +with a newer primary overriding an older nonprimary source. Both configured and +built-in hooks must run for the implicit local default. Task creation and controller +classification share configured-default, primary, creation-time and ID precedence. +Explicit choices and saved copy-only markers still suppress hooks; changing the +default must not rotate an existing binding or clear its marker. +SSH keeps its explicit remote provision-command path; these checks +do not claim a new sandbox provision transport. +Low-trust closed-worktree reopening, including a board-comment reopen of a projectless task, must also suppress both kinds of host provision script. Returned checkpoints must reject file, symlink or missing parents before host writes, even if a nested file is marked unchanged. The retained host file and recovery evidence must survive rejection. These are automated safety contracts, not new live runtime acceptance. + +For a child created by an admitted agent with an explicit existing workspace, verify that a matching sandbox boundary permits creation before the child has an ID. A changed child policy, unrelated parent, or forged creator run must fail. See the [child workspace trust regression](../../server/src/__tests__/task-workspace-child-trust.test.ts). This automated contract does not add live acceptance evidence. + ### `workspace-management` Automated: [workspace detail](../../ui/src/pages/ExecutionWorkspaceDetail.test.tsx) and [runtime service](../../server/src/__tests__/workspace-runtime.test.ts) cover UI and runtime contracts separately. @@ -49,4 +84,7 @@ Manual: Provision a disposable workspace, wait for readiness, start an available - A link or running badge does not prove the service is reachable from the user’s browser. - Workspace IDs, runtime leases, and repository paths are not interchangeable. +- Organizational project, source project, task files, personal `AGENT_HOME`, and + provider home have separate meanings. Private source ownership remains attached + to copied files after source configuration changes or workspace closure. - Use [recovery](./recovery.md) for divergence/export repair and [CLI operations](./cli-operations.md) for local worktree tooling. diff --git a/maps/storybook-map/INDEX.md b/maps/storybook-map/INDEX.md index f66a50ca757..0d334dc32c7 100644 --- a/maps/storybook-map/INDEX.md +++ b/maps/storybook-map/INDEX.md @@ -97,20 +97,20 @@ Status describes product alignment; implementation describes the rendered subjec | Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note | | --- | --- | --- | --- | --- | -| [Apps, channels, and extensions/External chat and email/Connections/AgentMail setup](../../ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 19 / 16 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/AgentMail settings](../../ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx) | `chat-channels/lifecycle` | unreviewed / production / fixtures | 8 / 5 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/AgentMail tasks](../../ui/storybook/stories/chat-channels/agentmail.stories.tsx) | `chat-channels/conversation` | unreviewed / prototype / fixtures | 9 / 2 | Local EmailTaskExperience combines production composer/bubbles with simulated email lifecycle; compare against IssueDetail before claiming page parity. | -| [Apps, channels, and extensions/External chat and email/Connections/Chat connection actions](../../ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx) | `chat-channels/providers` | unreviewed / production / fixtures | 1 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Chat management](../../ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx) | `chat-channels/lifecycle` | unreviewed / production / fixtures | 15 / 6 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connectors/GitHub bot management](../../ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx) | `chat-channels/providers`, `chat-channels/conversation` | unreviewed / production / fixtures | 33 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Historical/GitHub chat prototypes](../../ui/storybook/stories/chat-channels/github-chat.stories.tsx) | `chat-channels/providers`, `chat-channels/conversation` | historical / prototype / fixtures | 48 / 9 | Historical wizard retained for original setup/access/review design; current GitHub setup is ui/src/pages/apps/chat/GitHubChatSetup.tsx. | -| [Apps, channels, and extensions/External chat and email/Connections/GitHub and Code Review Bot](../../ui/storybook/stories/chat-channels/github-connections.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 7 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/iMessage Photon](../../ui/storybook/stories/chat-channels/imessage-photon.stories.tsx) | `chat-channels/conversation` | unreviewed / production / fixtures | 13 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Slack/Automatic setup](../../ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 18 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Slack/Avatar in Settings](../../ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx) | `chat-channels/identity` | current / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Slack/Communication in Settings](../../ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx) | `chat-channels/identity` | current / production / fixtures | 4 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Slack/Managed setup](../../ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 21 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Apps, channels, and extensions/External chat and email/Connections/Slack/Task tools](../../ui/storybook/stories/chat-channels/slack-tools.stories.tsx) | `chat-channels/conversation` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | +| [Apps, channels, and extensions/External chat and email/Connections/AgentMail setup](../../ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 19 / 16 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/AgentMail settings](../../ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx) | `chat-channels/lifecycle` | unreviewed / production / fixtures | 8 / 5 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: EmailEndpointSettings remains the mounted inbox subsection. The surrounding ChatEndpointDetail connection-default editor and its save/inheritance behavior are not represented here. | +| [Apps, channels, and extensions/External chat and email/Connections/AgentMail tasks](../../ui/storybook/stories/chat-channels/agentmail.stories.tsx) | `chat-channels/conversation` | unreviewed / prototype / fixtures | 9 / 2 | Local EmailTaskExperience combines production composer/bubbles with simulated email lifecycle; compare against IssueDetail before claiming page parity. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Chat connection actions](../../ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx) | `chat-channels/providers` | unreviewed / production / fixtures | 1 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Chat management](../../ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx) | `chat-channels/lifecycle` | unreviewed / production / fixtures | 15 / 6 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: the mounted endpoint adds connection and destination editors. Local endpoint PATCH/resource PUT preserve defaults; workspace-choice requests now return only active/idle, unclosed fixture workspaces. Browser save/error/reload and new-conversation inheritance remain unverified. | +| [Apps, channels, and extensions/External chat and email/Connectors/GitHub bot management](../../ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx) | `chat-channels/providers`, `chat-channels/conversation` | unreviewed / production / fixtures | 33 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: mounted repository and endpoint editors use simulated defaults. Endpoint PATCH now persists omitted/null/concrete values across GET and resets on remount; repository PUT already preserves them. Choice caches intentionally contain no projects/workspaces. Browser editor interactions, access denials and new-task inheritance remain unverified. | +| [Apps, channels, and extensions/External chat and email/Historical/GitHub chat prototypes](../../ui/storybook/stories/chat-channels/github-chat.stories.tsx) | `chat-channels/providers`, `chat-channels/conversation` | historical / prototype / fixtures | 48 / 9 | Historical wizard retained for original setup/access/review design; current GitHub setup is ui/src/pages/apps/chat/GitHubChatSetup.tsx. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/GitHub and Code Review Bot](../../ui/storybook/stories/chat-channels/github-connections.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 7 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/iMessage Photon](../../ui/storybook/stories/chat-channels/imessage-photon.stories.tsx) | `chat-channels/conversation` | unreviewed / production / fixtures | 13 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the endpoint editor addition; this suite selects setup/access and locally simulated conversation states. Endpoint PATCH retains fields, but settings workspace-choice requests and default selection are not covered by these scenarios. | +| [Apps, channels, and extensions/External chat and email/Connections/Slack/Automatic setup](../../ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 18 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Slack/Avatar in Settings](../../ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx) | `chat-channels/identity` | current / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Slack/Communication in Settings](../../ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx) | `chat-channels/identity` | current / production / fixtures | 4 / 1 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Slack/Managed setup](../../ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx) | `chat-channels/providers`, `connection-setup/channel-setup` | unreviewed / production / fixtures | 21 / 7 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | +| [Apps, channels, and extensions/External chat and email/Connections/Slack/Task tools](../../ui/storybook/stories/chat-channels/slack-tools.stories.tsx) | `chat-channels/conversation` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance. | ## companies @@ -179,9 +179,9 @@ Status describes product alignment; implementation describes the rendered subjec | [Tasks and conversations/Documents, attachments, and work products/Product/Documents/Annotations](../../ui/storybook/stories/documents-artifacts/document-annotations.stories.tsx) | `documents-artifacts/annotations` | unreviewed / production / fixtures | 9 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | | [Tasks and conversations/Documents, attachments, and work products/Product/Document Comments](../../ui/storybook/stories/documents-artifacts/document-comments.stories.tsx) | `documents-artifacts/annotations` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | | [Tasks and conversations/Documents, attachments, and work products/Components/File Viewer Sheet](../../ui/storybook/stories/documents-artifacts/file-viewer.stories.tsx) | `documents-artifacts/attachments` | unreviewed / production / fixtures | 13 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview](../../ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace](../../ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey](../../ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | +| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview](../../ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 5 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace](../../ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey](../../ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx) | `documents-artifacts/attachments`, `workspaces/files-git` | unreviewed / production / fixtures | 3 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | | [Tasks and conversations/Documents, attachments, and work products/Task Chat/Rich Work Product Cards](../../ui/storybook/stories/documents-artifacts/rich-work-product-cards.stories.tsx) | `documents-artifacts/work-products` | unreviewed / production / fixtures | 9 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | | [Tasks and conversations/Documents, attachments, and work products/Tasks/Artifact Gallery](../../ui/storybook/stories/documents-artifacts/task-artifact-gallery.stories.tsx) | `documents-artifacts/library`, `documents-artifacts/work-products` | unreviewed / production / fixtures | 8 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | | [Tasks and conversations/Documents, attachments, and work products/Tasks/Text file tabs](../../ui/storybook/stories/documents-artifacts/task-text-files.stories.tsx) | `documents-artifacts/attachments` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | @@ -257,9 +257,9 @@ Status describes product alignment; implementation describes the rendered subjec | Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note | | --- | --- | --- | --- | --- | | [Projects and execution/Projects and repositories/Proposals/Project repos/New project](../../ui/storybook/stories/projects/new-project-repos.stories.tsx) | `projects/repositories` | proposed / mixed / fixtures | 18 / 5 | RepositoryEditor is shipped, but NewProjectPrototype composes its own creation flow. Preserve proposal until route parity is checked. | -| [Projects and execution/Projects and repositories/Workspaces/Project runtime provision command](../../ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx) | `projects/defaults`, `workspaces/provisioning` | unreviewed / production / fixtures | 2 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | +| [Projects and execution/Projects and repositories/Workspaces/Project runtime provision command](../../ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx) | `projects/defaults`, `workspaces/provisioning` | unreviewed / production / fixtures | 2 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | | [Projects and execution/Projects and repositories/Proposals/Project repos/Configuration](../../ui/storybook/stories/projects/project-repos-configuration.stories.tsx) | `projects/repositories` | proposed / mixed / fixtures | 12 / 0 | Production properties plus local configuration page composition; repository interaction is simulated. | -| [Projects and execution/Projects and repositories/Product/Projects Goals Workspaces](../../ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx) | `projects/lifecycle`, `goals/hierarchy`, `workspaces/binding` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | +| [Projects and execution/Projects and repositories/Product/Projects Goals Workspaces](../../ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx) | `projects/lifecycle`, `goals/hierarchy`, `workspaces/binding` | unreviewed / production / fixtures | 1 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | ## questions-and-approvals @@ -424,13 +424,13 @@ Status describes product alignment; implementation describes the rendered subjec | Story suite | Capabilities | Product / implementation / data | Stories / play functions | Review note | | --- | --- | --- | --- | --- | -| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity](../../ui/storybook/stories/workspaces/browser-use-activity.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer](../../ui/storybook/stories/workspaces/browser-use-footer.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 21 / 2 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel](../../ui/storybook/stories/workspaces/browser-use.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 28 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker](../../ui/storybook/stories/workspaces/searchable-select.stories.tsx) | `workspaces/binding` | unreviewed / production / fixtures | 9 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser](../../ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx) | `workspaces/files-git` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status](../../ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx) | `workspaces/provisioning` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | -| [Projects and execution/Workspaces, services, and files/Workspaces/Service control bar](../../ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx) | `workspaces/services` | unreviewed / production / fixtures | 16 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. | +| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity](../../ui/storybook/stories/workspaces/browser-use-activity.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 7 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer](../../ui/storybook/stories/workspaces/browser-use-footer.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 21 / 2 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel](../../ui/storybook/stories/workspaces/browser-use.stories.tsx) | `workspaces/services`, `tasks/discussion` | unreviewed / production / fixtures | 28 / 3 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker](../../ui/storybook/stories/workspaces/searchable-select.stories.tsx) | `workspaces/binding` | unreviewed / production / fixtures | 9 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser](../../ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx) | `workspaces/files-git` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status](../../ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx) | `workspaces/provisioning` | unreviewed / production / fixtures | 6 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | +| [Projects and execution/Workspaces, services, and files/Workspaces/Service control bar](../../ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx) | `workspaces/services` | unreviewed / production / fixtures | 16 / 0 | Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence. | ## Capability lookup diff --git a/maps/storybook-map/REVIEW.md b/maps/storybook-map/REVIEW.md index a914397c46f..1c2c8c6264f 100644 --- a/maps/storybook-map/REVIEW.md +++ b/maps/storybook-map/REVIEW.md @@ -158,3 +158,58 @@ reasons below. The owned static preview was left running for the requested test- Use `maintain-storybook` to correct drift and `review-storybook` to record separate source, render, interaction, visual-comparison, and production evidence. Both skills consume the feature map; neither changes its upstream contract. + +## Task workspace reconciliation — 2026-10-12 + +Scope: the 26 declared-source entries affected by task-workspace work at +`88b1d3a2`, compared with master `dabb8c5daf`, including subsequent comment-only +cleanup and the fixture corrections below. This is a bounded source review, +not a new review of all Storybook states. Reconciliation onto `b5046ec022` +preserves its new feature folders, sidebar titles and explicit stable IDs, along +with the existing exports, classification and simulation boundaries. Shared GitHub +fixtures now live under `ui/storybook/support/github-bot-management/`. + +Twenty-one entries changed only through the expanded chat/workspace recipes. +Their setup, identity, browser, artifact, picker and service examples are retained; +the new recipe requirements do not become covered merely because they reference +the same capability. Five entries also depend on changed production subjects: + +| Story file under `ui/storybook/stories/` | Source review and remaining scope | +| --- | --- | +| `chat-channels/chat-management/chat-management.stories.tsx` | Mounted endpoint includes connection/destination defaults. Added the missing selectable-workspace fixture response; existing endpoint PATCH and resource PUT retain defaults. No browser save, error, reload or inheritance proof in this pass. | +| `chat-channels/github-bot-management/management.stories.tsx` | Mounted endpoint/repository editors include defaults. Fixed the endpoint mock to persist PATCH values instead of returning the original endpoint. Resource PUT already distinguishes omitted, null and concrete values. Project/workspace choices are intentionally empty fixtures. | +| `chat-channels/imessage-photon.stories.tsx` | Existing scenarios mount setup/access and simulate task conversations. They do not exercise the newly added settings editor or its workspace-choice request. Kept unreviewed rather than claiming settings coverage. | +| `projects/projects-goals-workspaces.stories.tsx` | Existing project-bound summaries still match the rendered components. New nullable project references and runtime invalidation branches have no projectless story scenario here. | +| `workspaces/workspace-runtime-provisioning.stories.tsx` | The demonstrated status subcomponent is unchanged. Its local context row does not prove the full detail page's new projectless behavior. | + +The three endpoint-management entries now declare `ChatExecutionDefaults` and its +API clients as review dependencies. AgentMail settings still demonstrates only +the inbox subsection, not the surrounding endpoint defaults. Historical GitHub +designs remain historical; existing unreviewed entries remain unreviewed. None of +these fixture examples establishes source authorization, retained file privacy, +pending workspace selection, sandbox admission or existing-conversation continuity. + +Executed: the GitHub fixture's two Vitest cases passed, including default save, +subsequent GET, omitted-value preservation, explicit null clearing and fresh-mount +reset. After the folder rebase, both fixture tests and all nine catalog checker +tests passed again; token gates passed before the path-only reconciliation. After the final +source freeze, only the 26 reviewed fingerprints were refreshed; catalog validation +passed for all 223 files and all 2,210 source-parsed story IDs were unchanged. +All 223 upstream file paths, explicit stable IDs and sidebar titles were preserved. +Not run in this pass: Storybook build, browser rendering or `play`, screenshot +comparisons, provider calls and live production acceptance. No new visual +baselines were accepted. Fixture tests are boundary simulations, not server +persistence or permission tests. + +## Workspace safety recipe follow-up — 2026-10-12 + +Reviewed the twelve entries that depend on the workspace feature recipe: browser-use activity/footer/page, the three HTML-artifact suites, project execution strategy and project workspace summaries, searchable selection, workspace files, runtime provisioning and service controls. Their production subjects, fixtures, feature folders, sidebar titles and stable IDs are unchanged. The recipe now requires prospective child boundary checks, safe low-trust reopening, and coherent returned checkpoint ancestry. These backend safety contracts are not exercised by those fixtures. Each entry remains unreviewed for full production parity. Only the reviewed recipe fingerprints were refreshed; no browser render, play, screenshot baseline or production acceptance was run for this follow-up. + + +## Remote provisioning recipe follow-up — 2026-10-13 + +Reviewed the same twelve workspace-recipe-dependent entries after extending the controller-provisioning boundary to standard-trust sandbox and SSH selections. Their components, fixtures and stable IDs are unchanged. Project strategy stories demonstrate configuration fields with environment selection disabled; runtime provisioning stories demonstrate status values with a no-operation log handler. Browser, artifact, file and service-control fixtures do not execute source scripts or prove admission placement. The new recipe requires controller marker denial independent of source write grants while copying and dispatch remain available, including local copy-only selections, retained deny-only metadata and closed-worktree reopening. Implicit trusted local defaults remain separate positive controls. All twelve entries retain their unreviewed production classification. Refreshed only these recipe fingerprints after this source review; no browser render, play, screenshot, Storybook build or live runtime qualification is claimed. + +## Default-source recipe follow-up — 2026-10-13 + +Reviewed the twelve unchanged workspace recipe subjects and fixtures after correcting implicit default selection. Task creation and controller classification now share configured-policy, primary, creation-time and ID precedence. Explicit choices and retained denial markers still suppress controller hooks. Project strategy fixtures show configuration, while runtime status and file/browser/artifact/service fixtures do not execute source scripts or prove this backend ordering. Refreshed only their reviewed recipe fingerprints and retained all classifications and stable IDs. No browser render, play, screenshot, Storybook build or live runtime acceptance was performed. diff --git a/maps/storybook-map/catalog.json b/maps/storybook-map/catalog.json index 3bdb0a34160..c376bdce637 100644 --- a/maps/storybook-map/catalog.json +++ b/maps/storybook-map/catalog.json @@ -633,7 +633,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -643,7 +643,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/agentmail-onboarding.stories.tsx" ], - "fingerprint": "a1d017e03f98f45224826a5c6a2bebeecb8a9fc42eba667a8164d0be6168f735", + "fingerprint": "e25771274378d2ff2f2447b2841ff20b91319dba4e50f697c5b2c6cc74698b4b", "stableId": "connections-agentmail-setup", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/AgentMail setup" }, @@ -656,14 +656,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: EmailEndpointSettings remains the mounted inbox subsection. The surrounding ChatEndpointDetail connection-default editor and its save/inheritance behavior are not represented here.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", "ui/src/pages/apps/chat/EmailEndpointSetup.tsx", "ui/storybook/stories/chat-channels/agentmail-settings.stories.tsx" ], - "fingerprint": "1fda3e7570e13c73c9bb9908efe05508e3eb14c716a7c9462ae6a370b77b5a5c", + "fingerprint": "a57256936459ba14d7ad8e0a73ed494c0f269c294240a67c0d27af555e0c736d", "stableId": "connections-agentmail-settings", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/AgentMail settings" }, @@ -676,7 +676,7 @@ "status": "unreviewed", "implementation": "prototype", "boundary": "fixtures", - "notes": "Local EmailTaskExperience combines production composer/bubbles with simulated email lifecycle; compare against IssueDetail before claiming page parity.", + "notes": "Local EmailTaskExperience combines production composer/bubbles with simulated email lifecycle; compare against IssueDetail before claiming page parity. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -687,7 +687,7 @@ "ui/src/components/task-chat/presentation-mode.tsx", "ui/storybook/stories/chat-channels/agentmail.stories.tsx" ], - "fingerprint": "9f476aac6ea2ecd4a5cb72226b460fc1c78e3bfda05926d6bfd1b8581e68fdcd", + "fingerprint": "4433454864b43dce30061f1b7e8f65e7bc5fa2f386bde115e276c30de4e6898f", "stableId": "connections-agentmail-tasks", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/AgentMail tasks" }, @@ -1208,8 +1208,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/tasks.md", "maps/feature-map/workspaces.md", @@ -1219,7 +1219,7 @@ "ui/storybook/fixtures/browser-use.tsx", "ui/storybook/stories/workspaces/browser-use-activity.stories.tsx" ], - "fingerprint": "0fb038daa922a0b61864f4ebf0096a50c9caf5dc290887cbf97a0bb63dad22af", + "fingerprint": "101840c76686fbf674f97ab469410466a57ff6ed733950544f7fe18b48b98376", "stableId": "tasks-browser-use-task-activity", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Task activity" }, @@ -1233,8 +1233,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/tasks.md", "maps/feature-map/workspaces.md", @@ -1243,7 +1243,7 @@ "ui/storybook/fixtures/browser-use.tsx", "ui/storybook/stories/workspaces/browser-use-footer.stories.tsx" ], - "fingerprint": "f42487c77335ac64cbe0822c51b1706098f26d9b5823e20837089f261277fd58", + "fingerprint": "8cf7c9c98d9626150ab7ee48bf81ab5c98e1832b5b1c38ed4a47d5e15e0be9dc", "stableId": "tasks-browser-use-footer", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Footer" }, @@ -1279,8 +1279,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/tasks.md", "maps/feature-map/workspaces.md", @@ -1290,7 +1290,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/workspaces/browser-use.stories.tsx" ], - "fingerprint": "ce0948c8a9dade2bcab999dccf4f5daa1ddbc4f3ecc72a70e88a1f40861d5199", + "fingerprint": "3a9d780a83fe5a79fd8209125debc6292b2dbcffdf40cf0b0935e442fc8cb7a8", "stableId": "tasks-browser-use-panel", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Tasks/Browser Use/Panel" }, @@ -1387,14 +1387,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", "ui/src/pages/apps/Browse.tsx", "ui/storybook/stories/chat-channels/chat-connection-actions.stories.tsx" ], - "fingerprint": "c80c99044431f1cfe75e7b3da1b28d776595217d5bbda14aa82c78b0b272aab0", + "fingerprint": "b5c4a820f42d7b3d5e665161b95056b80b5591f6100e6847488eea49b876d36d", "stableId": "connections-chat-connection-actions", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Chat connection actions" }, @@ -1443,16 +1443,20 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: the mounted endpoint adds connection and destination editors. Local endpoint PATCH/resource PUT preserve defaults; workspace-choice requests now return only active/idle, unclosed fixture workspaces. Browser save/error/reload and new-conversation inheritance remain unverified.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", + "ui/src/api/chatEndpoints.ts", + "ui/src/api/execution-workspaces.ts", + "ui/src/api/projects.ts", "ui/src/components/Layout.tsx", "ui/src/pages/apps/chat/ChatEndpointDetail.tsx", + "ui/src/pages/apps/chat/ChatExecutionDefaults.tsx", "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/chat-management/chat-management.stories.tsx" ], - "fingerprint": "5025a812c84d5700fea3907dd09f484ae3ded0d2f1cfc0f8e83f0fb5bc6d181e", + "fingerprint": "593f980a74e21b1e8bce9f48ee8ef7f1247546e4b4519a0916b64881ba5224c8", "stableId": "connections-chat-management", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Chat management" }, @@ -2521,14 +2525,18 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation: mounted repository and endpoint editors use simulated defaults. Endpoint PATCH now persists omitted/null/concrete values across GET and resets on remount; repository PUT already preserves them. Choice caches intentionally contain no projects/workspaces. Browser editor interactions, access denials and new-task inheritance remain unverified.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", + "ui/src/api/chatEndpoints.ts", + "ui/src/api/execution-workspaces.ts", + "ui/src/api/projects.ts", "ui/src/components/Layout.tsx", "ui/src/components/OnboardingWizard.tsx", "ui/src/pages/apps/chat/ChatConversationList.tsx", "ui/src/pages/apps/chat/ChatEndpointDetail.tsx", + "ui/src/pages/apps/chat/ChatExecutionDefaults.tsx", "ui/src/pages/apps/chat/GitHubAppIdentity.tsx", "ui/src/pages/apps/chat/GitHubBotConfiguration.tsx", "ui/src/pages/apps/chat/GitHubBotManagement.tsx", @@ -2536,11 +2544,11 @@ "ui/src/pages/apps/chat/GitHubConnectionComplete.tsx", "ui/storybook/fixtures/onboardingDraft.ts", "ui/storybook/fixtures/paperclipData.ts", + "ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx", "ui/storybook/support/github-bot-management/fixture-api.tsx", - "ui/storybook/support/github-bot-management/fixtures.ts", - "ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx" + "ui/storybook/support/github-bot-management/fixtures.ts" ], - "fingerprint": "9a432840be84fdfd9e437b851d5a975d13edba7eeca4c6273eb21cf69ed29e32", + "fingerprint": "8688291f425ae517d5a655e9ab237bb035ef2cc8853dfc24129bd7258e36acab", "stableId": "connectors-github-bot-management", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connectors/GitHub bot management" }, @@ -2554,7 +2562,7 @@ "status": "historical", "implementation": "prototype", "boundary": "fixtures", - "notes": "Historical wizard retained for original setup/access/review design; current GitHub setup is ui/src/pages/apps/chat/GitHubChatSetup.tsx.", + "notes": "Historical wizard retained for original setup/access/review design; current GitHub setup is ui/src/pages/apps/chat/GitHubChatSetup.tsx. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -2567,7 +2575,7 @@ "ui/storybook/stories/chat-channels/github-chat.stories.tsx" ], "replacement": "ui/storybook/stories/chat-channels/github-connections.stories.tsx", - "fingerprint": "acb56c908e107266128dd0d817162007a0a7ec75b24fa059714cee490e4ef642", + "fingerprint": "1b98e4a1e023630f9d27105f47ee8e04c7e49e31f1eb77430e55cf0518bca033", "stableId": "historical-github-chat-prototypes", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Historical/GitHub chat prototypes" }, @@ -2581,7 +2589,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -2593,7 +2601,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/github-connections.stories.tsx" ], - "fingerprint": "737355c3a9a9e1fe69b606edf543d0e7f0b94b7332a344a0f077c4cccfb544b9", + "fingerprint": "1db66005c3710754f307003ce6907073e8eca0319074c323d57992c0b26d8b23", "stableId": "connections-github-and-code-review-bot", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/GitHub and Code Review Bot" }, @@ -2656,8 +2664,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/documents-artifacts.md", "maps/feature-map/workspaces.md", @@ -2665,7 +2673,7 @@ "ui/storybook/stories/documents-artifacts/html-artifacts/01-preview.stories.tsx", "ui/storybook/support/html-artifacts/fixtures.ts" ], - "fingerprint": "7289b022aeb669441639942cb709e07a1f03f1cf4e0ca5db3672c654a50da92a", + "fingerprint": "9f96f1e4abf214d663906e05b0a8ce231f06217fc5f022415a84df33c1d0ac96", "stableId": "html-artifacts-01-preview", "sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/01 Preview" }, @@ -2679,8 +2687,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/documents-artifacts.md", "maps/feature-map/workspaces.md", @@ -2689,7 +2697,7 @@ "ui/storybook/stories/documents-artifacts/html-artifacts/02-workspace.stories.tsx", "ui/storybook/support/html-artifacts/fixtures.ts" ], - "fingerprint": "2463cc23aa39bca80133e8b19bb4899f9dfbab7272747796db7855da51642ec2", + "fingerprint": "47c33fce5f296031d43f31d7f099abcef40393cfe51e565db542b7e8230a00a3", "stableId": "html-artifacts-02-workspace", "sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/02 Workspace" }, @@ -2703,8 +2711,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/documents-artifacts.md", "maps/feature-map/workspaces.md", @@ -2716,7 +2724,7 @@ "ui/storybook/stories/documents-artifacts/html-artifacts/03-task-journey.stories.tsx", "ui/storybook/support/html-artifacts/fixtures.ts" ], - "fingerprint": "d2b3ecd2b4490ca3bec6c8dfacad4c02b794f78fd4d348c6c0fd165fb8928f33", + "fingerprint": "470396b938981d10ddc74d03cec0808aaa1fb305dcdeb8056b308c2752f7cea7", "stableId": "html-artifacts-03-task-journey", "sidebarTitle": "Tasks and conversations/Documents, attachments, and work products/HTML artifacts/03 Task journey" }, @@ -2729,19 +2737,23 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the endpoint editor addition; this suite selects setup/access and locally simulated conversation states. Endpoint PATCH retains fields, but settings workspace-choice requests and default selection are not covered by these scenarios.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", + "ui/src/api/chatEndpoints.ts", + "ui/src/api/execution-workspaces.ts", + "ui/src/api/projects.ts", "ui/src/components/task-chat/TaskChatBubble.tsx", "ui/src/components/task-chat/task-chat-model.ts", "ui/src/pages/apps/Browse.tsx", "ui/src/pages/apps/chat/ChatEndpointDetail.tsx", "ui/src/pages/apps/chat/ChatEndpointSetup.tsx", + "ui/src/pages/apps/chat/ChatExecutionDefaults.tsx", "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/imessage-photon.stories.tsx" ], - "fingerprint": "e808e8533ef84eaa5f0c49144f7fa23554a0956217177a25fe7386ddaf4c9ffc", + "fingerprint": "90d113f72e3f6db48428bf498fba3d14d713eaab194bfb66eb1d27fe17843063", "stableId": "connections-imessage-photon", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/iMessage Photon" }, @@ -3928,8 +3940,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/projects.md", "maps/feature-map/workspaces.md", @@ -3937,7 +3949,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/projects/project-execution-workspace-strategy.stories.tsx" ], - "fingerprint": "d3c1ddd40e4acf1b30ee80655773c475e71885b75fe61b94c5a7ab1010fcb7ed", + "fingerprint": "618a389f8aa005657fc633c9a0ebdc34e0fc77df6d1cc8d0474330590d4c081c", "stableId": "workspaces-project-runtime-provision-command", "sidebarTitle": "Projects and execution/Projects and repositories/Workspaces/Project runtime provision command" }, @@ -3981,8 +3993,8 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed nullable project references and guarded runtime invalidations. Existing summaries remain project-bound and retain their routes; null-project execution workspace rendering and controls remain a coverage gap. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/goals.md", "maps/feature-map/projects.md", @@ -3997,7 +4009,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/projects/projects-goals-workspaces.stories.tsx" ], - "fingerprint": "4aa89b2f36fd6db30b20528b2e95063a8846279d28623f927de35acd75ce64f5", + "fingerprint": "83a6815f2ceea9c18eb9145f2abf7f90588dafe833c20abcd93050ee1c66114a", "stableId": "product-projects-goals-workspaces", "sidebarTitle": "Projects and execution/Projects and repositories/Product/Projects Goals Workspaces" }, @@ -4666,14 +4678,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/workspaces.md", "ui/src/components/SearchableSelect.tsx", "ui/storybook/stories/workspaces/searchable-select.stories.tsx" ], - "fingerprint": "a09a2ecf9c05843dc29ea4fd173fa7e9b0968defc8a83d48b2ffc81503b2f98f", + "fingerprint": "524803af7c926b60e8c636031f703d4bb4abe45cd4832c7985f81c8f4d0cb035", "stableId": "components-searchableselect-workspace-picker", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Components/SearchableSelect/Workspace picker" }, @@ -4814,7 +4826,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -4825,7 +4837,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/slack-automatic-setup.stories.tsx" ], - "fingerprint": "42fb2d53434f7ba8cc5cce556818d6469e02da559bd40fb6dbd4fd5bec9aecb5", + "fingerprint": "df0600372c76d7ff7eb15ccc14a7a5bf7f0b5f5d64a366013686b6aaf888ee00", "stableId": "connections-slack-automatic-setup", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Slack/Automatic setup" }, @@ -4838,7 +4850,7 @@ "status": "current", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -4847,7 +4859,7 @@ "ui/storybook/prototypes/slack-avatar/SlackAvatarSettingsPreview.tsx", "ui/storybook/stories/chat-channels/slack-avatar-settings.stories.tsx" ], - "fingerprint": "ed2053a543507e9ad110084b6974204897193fcc1d7a1c4cb23f15e1cfe5dcba", + "fingerprint": "725869cfc3280b8384899e0f692bcf17663368f389c38fe1a16ec34c27faf20d", "stableId": "connections-slack-avatar-in-settings", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Slack/Avatar in Settings" }, @@ -4860,7 +4872,7 @@ "status": "current", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -4869,7 +4881,7 @@ "ui/storybook/prototypes/slack-avatar/SlackAvatarSettingsPreview.tsx", "ui/storybook/stories/chat-channels/slack-communication-settings.stories.tsx" ], - "fingerprint": "c581843a009938f16173f6874a1edbe6a562c3c4382b3a674fcd3487d37a50ce", + "fingerprint": "f0d8df504be6e6e5a1ea09b868ee8a75e35d76041a792f48955dc1e834297cb6", "stableId": "connections-slack-communication-in-settings", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Slack/Communication in Settings" }, @@ -4883,7 +4895,7 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", @@ -4894,7 +4906,7 @@ "ui/storybook/fixtures/paperclipData.ts", "ui/storybook/stories/chat-channels/slack-managed-setup.stories.tsx" ], - "fingerprint": "d947a4bd8dec88fd77c0b68e51ff12bab050342661e455a4f72443d82916d7c9", + "fingerprint": "24e3f986568fad11d604fddc48db890e2c75091b8b7c0ff916c6d609dd4c033d", "stableId": "connections-slack-managed-setup", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Slack/Managed setup" }, @@ -4907,14 +4919,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the added connection/destination-default recipe; these unchanged setup, identity or conversation examples do not establish default selection or new-task inheritance.", "reviewedAt": "2026-10-12", "sources": [ "maps/feature-map/chat-channels.md", "ui/src/pages/apps/chat/SlackToolSettings.tsx", "ui/storybook/stories/chat-channels/slack-tools.stories.tsx" ], - "fingerprint": "a8464a4cbed26f72dacc14e77326caa7d649b23eec075729667d48f267bb8a03", + "fingerprint": "9eaae8672c83f1be3d6bdea09e6a77fd62f2828ab410addfe82f9c08c3c34a4a", "stableId": "connections-slack-task-tools", "sidebarTitle": "Apps, channels, and extensions/External chat and email/Connections/Slack/Task tools" }, @@ -5533,15 +5545,15 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/workspaces.md", "ui/src/components/FileViewerSheet.tsx", "ui/src/components/IssueWorkspaceCard.tsx", "ui/storybook/stories/workspaces/workspace-file-browser.stories.tsx" ], - "fingerprint": "e8ece699c3f156643552e788ba86cedda4f2e3a4e6e1a40fbf8b6bef1103254f", + "fingerprint": "980fcd9a6905ac1eae10efeb7979dd8b3769c2cc7d625d4e0ba4b091f4b4d614", "stableId": "issue-workspace-file-browser", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Issue/Workspace File Browser" }, @@ -5554,14 +5566,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed ExecutionWorkspaceDetail null-project handling. This suite mounts only the unchanged RuntimeProvisionStatusValue inside a local context row; it does not exercise the full detail page or projectless queries. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/workspaces.md", "ui/src/pages/ExecutionWorkspaceDetail.tsx", "ui/storybook/stories/workspaces/workspace-runtime-provisioning.stories.tsx" ], - "fingerprint": "4ec295039bb17a9590ab1fb0be2864845a903a625fcaf79459541ddd5a4670f4", + "fingerprint": "f8a7316fb6637151d8da32b637136408cd6fd9d505fe2a2a47f3c0d670b0eeec", "stableId": "workspaces-runtime-provisioning-status", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Workspaces/Runtime provisioning status" }, @@ -5574,14 +5586,14 @@ "status": "unreviewed", "implementation": "production", "boundary": "fixtures", - "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed.", - "reviewedAt": "2026-10-12", + "notes": "Shipped subjects identified through imports and local wrappers; full state, fixture-contract and route parity remain unreviewed. Task-workspace reconciliation reviewed the expanded workspace recipe; these examples do not establish durable task-folder continuity, repository authorization or sandbox admission. The workspace recipe now requires child boundary, safe reopen and checkpoint ancestry controls; these backend checks are not established by this fixture. The provisioning recipe requires controller script denial for remote selections and copy-only local source choices, including retained denial through metadata changes and reopening; these fixtures do not verify execution placement. Canonical default provisioning follows configured policy, primary, creation time and ID; these fixtures do not run source hooks or establish this backend precedence.", + "reviewedAt": "2026-10-13", "sources": [ "maps/feature-map/workspaces.md", "ui/src/components/WorkspaceServiceControlBar.tsx", "ui/storybook/stories/workspaces/workspace-service-control-bar.stories.tsx" ], - "fingerprint": "cbe4b5d61324f8eec1dfff9583dc84eab5c83947ff947f5c381d00217b29b5e2", + "fingerprint": "b9cd51c58b31ab445e2997b9f2a23c7a7b3f4ecd7b6dd39fc0f35ae0779311df", "stableId": "workspaces-service-control-bar", "sidebarTitle": "Projects and execution/Workspaces, services, and files/Workspaces/Service control bar" } diff --git a/packages/adapter-utils/src/command-managed-runtime.ts b/packages/adapter-utils/src/command-managed-runtime.ts index 027f8489f02..0fad2165327 100644 --- a/packages/adapter-utils/src/command-managed-runtime.ts +++ b/packages/adapter-utils/src/command-managed-runtime.ts @@ -1,3 +1,4 @@ +import type { WorkspaceCheckpointMetrics } from "./workspace-checkpoint.js"; import { promises as fs } from "node:fs"; import { randomUUID } from "node:crypto"; import os from "node:os"; @@ -545,10 +546,14 @@ export async function prepareCommandManagedRuntime(input: { syncWorkspace?: boolean; workspaceInboundMode?: WorkspaceInboundMode; workspaceDurableSeed?: WorkspaceDurableSeedPaths; + workspaceCheckpoint?: boolean; + onWorkspaceCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise; + workspaceSeedCacheDirectory?: string; + workspaceSeedCacheCompanyDirectory?: string; workspaceBaseline?: DirectorySnapshot; workspaceGitSnapshot?: GitWorkspaceSnapshot | null; + workspaceRepositories?: NonNullable; workspaceExclude?: string[]; - /** Plain persistent directories include all files, independent of Git and task cache exclusions. */ workspaceFileMode?: "all"; preserveAbsentOnRestore?: string[]; assets?: CommandManagedRuntimeAsset[]; @@ -615,8 +620,13 @@ export async function prepareCommandManagedRuntime(input: { syncWorkspace: input.syncWorkspace, workspaceInboundMode: input.workspaceInboundMode, workspaceDurableSeed: input.workspaceDurableSeed, + workspaceCheckpoint: input.workspaceCheckpoint, + onWorkspaceCheckpoint: input.onWorkspaceCheckpoint, + workspaceSeedCacheDirectory: input.workspaceSeedCacheDirectory, + workspaceSeedCacheCompanyDirectory: input.workspaceSeedCacheCompanyDirectory, workspaceBaseline: input.workspaceBaseline, workspaceGitSnapshot: input.workspaceGitSnapshot, + workspaceRepositories: input.workspaceRepositories, workspaceExclude: mergeRuntimeExcludes(input.workspaceExclude), workspaceFileMode: input.workspaceFileMode, preserveAbsentOnRestore: input.preserveAbsentOnRestore, @@ -661,8 +671,13 @@ export async function prepareCommandManagedRuntime(input: { syncWorkspace: input.syncWorkspace, workspaceInboundMode: input.workspaceInboundMode, workspaceDurableSeed: input.workspaceDurableSeed, + workspaceCheckpoint: input.workspaceCheckpoint, + onWorkspaceCheckpoint: input.onWorkspaceCheckpoint, + workspaceSeedCacheDirectory: input.workspaceSeedCacheDirectory, + workspaceSeedCacheCompanyDirectory: input.workspaceSeedCacheCompanyDirectory, workspaceBaseline: input.workspaceBaseline, workspaceGitSnapshot: input.workspaceGitSnapshot, + workspaceRepositories: input.workspaceRepositories, workspaceExclude: mergeRuntimeExcludes(input.workspaceExclude), workspaceFileMode: input.workspaceFileMode, preserveAbsentOnRestore: input.preserveAbsentOnRestore, diff --git a/packages/adapter-utils/src/execution-target.ts b/packages/adapter-utils/src/execution-target.ts index 5e99328fa0b..5a5b9d79b78 100644 --- a/packages/adapter-utils/src/execution-target.ts +++ b/packages/adapter-utils/src/execution-target.ts @@ -1,3 +1,4 @@ +import type { WorkspaceCheckpointMetrics } from "./workspace-checkpoint.js"; import fs from "node:fs/promises"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; @@ -267,6 +268,7 @@ export interface PreparedAdapterExecutionTargetRuntime { workspaceSyncSnapshot: { baseline: DirectorySnapshot; gitSnapshot: GitWorkspaceSnapshot | null; + repositories?: NonNullable; } | null; cleanupWorkspaceSnapshot?(): Promise; restoreWorkspace(onProgress?: RuntimeProgressSink): Promise; @@ -1438,10 +1440,14 @@ export async function prepareAdapterExecutionTargetRuntime(input: { syncWorkspace?: boolean; workspaceInboundMode?: WorkspaceInboundMode; workspaceDurableSeed?: WorkspaceDurableSeedPaths; + workspaceCheckpoint?: boolean; + onWorkspaceCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise; + workspaceSeedCacheDirectory?: string; + workspaceSeedCacheCompanyDirectory?: string; workspaceBaseline?: DirectorySnapshot; workspaceGitSnapshot?: GitWorkspaceSnapshot | null; + workspaceRepositories?: NonNullable; workspaceExclude?: string[]; - /** Plain persistent directories include all files, independent of Git and task cache exclusions. */ workspaceFileMode?: "all"; preserveAbsentOnRestore?: string[]; assets?: AdapterManagedRuntimeAsset[]; @@ -1528,8 +1534,13 @@ export async function prepareAdapterExecutionTargetRuntime(input: { syncWorkspace: input.syncWorkspace, workspaceInboundMode: input.workspaceInboundMode, workspaceDurableSeed: input.workspaceDurableSeed, + workspaceCheckpoint: input.workspaceCheckpoint, + onWorkspaceCheckpoint: input.onWorkspaceCheckpoint, + workspaceSeedCacheDirectory: input.workspaceSeedCacheDirectory, + workspaceSeedCacheCompanyDirectory: input.workspaceSeedCacheCompanyDirectory, workspaceBaseline: input.workspaceBaseline, workspaceGitSnapshot: input.workspaceGitSnapshot, + workspaceRepositories: input.workspaceRepositories, workspaceExclude: input.workspaceExclude, workspaceFileMode: input.workspaceFileMode, preserveAbsentOnRestore: input.preserveAbsentOnRestore, diff --git a/packages/adapter-utils/src/git-workspace-sync.ts b/packages/adapter-utils/src/git-workspace-sync.ts index fcd6bd763dd..d49486aaebc 100644 --- a/packages/adapter-utils/src/git-workspace-sync.ts +++ b/packages/adapter-utils/src/git-workspace-sync.ts @@ -15,6 +15,7 @@ export interface GitCommandResult { export interface GitWorkspaceSnapshot { headCommit: string; branchName: string | null; + originUrl?: string | null; overlayPaths: WorkspacePaths; deletedPaths: WorkspacePaths; ignoredPaths: WorkspacePaths; @@ -168,6 +169,33 @@ export function workspaceSnapshotTimeoutMs(): number { return Number.isFinite(configured) && configured >= 1000 ? Math.min(configured, 86_400_000) : 30 * 60_000; } +export async function readManagedWorkspaceRepositories(localDir: string, options: { signal?: AbortSignal } = {}): Promise> { + const repositories: NonNullable = []; + try { + + const root = path.join(localDir, PROJECT_REPOSITORIES_DIR); + const rootStat = await fs.lstat(root).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (rootStat) { + if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Invalid project repositories directory"); + for await (const entry of await fs.opendir(root)) { + if (!entry.isDirectory() || !/^[a-zA-Z0-9_-]+$/.test(entry.name)) throw new Error("Invalid project repository directory"); + const relative = `${PROJECT_REPOSITORIES_DIR}/${entry.name}`; + const snapshot = await readGitWorkspaceSnapshot(path.join(localDir, relative), false, options); + if (!snapshot) throw new Error(`Project repository is not a Git checkout: ${relative}`); + repositories.push({ path: relative, snapshot }); + } + } + + return repositories; + } catch (error) { + for (const repo of repositories) await disposeGitWorkspaceSnapshot(repo.snapshot); + throw error; + } +} + export async function readGitWorkspaceSnapshot(localDir: string, includeRepositories = true, options: { signal?: AbortSignal } = {}): Promise { const repositories: NonNullable = []; // Only repository discovery may report an ordinary directory. A failed @@ -205,23 +233,7 @@ export async function readGitWorkspaceSnapshot(localDir: string, includeReposito options.signal?.addEventListener("abort", abort, { once: true }); if (options.signal?.aborted) abort(); try { - if (includeRepositories) { - const root = path.join(localDir, PROJECT_REPOSITORIES_DIR); - const rootStat = await fs.lstat(root).catch((error: NodeJS.ErrnoException) => { - if (error.code === "ENOENT") return null; - throw error; - }); - if (rootStat) { - if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Invalid project repositories directory"); - for await (const entry of await fs.opendir(root)) { - if (!entry.isDirectory() || !/^[a-zA-Z0-9_-]+$/.test(entry.name)) throw new Error("Invalid project repository directory"); - const relative = `${PROJECT_REPOSITORIES_DIR}/${entry.name}`; - const snapshot = await readGitWorkspaceSnapshot(path.join(localDir, relative), false, options); - if (!snapshot) throw new Error(`Project repository is not a Git checkout: ${relative}`); - repositories.push({ path: relative, snapshot }); - } - } - } + if (includeRepositories) repositories.push(...await readManagedWorkspaceRepositories(localDir, options)); const scan = async (args: string[], operation: string, category: string) => { const parser = new WorkspaceNulParser((record) => { @@ -267,6 +279,7 @@ export async function readGitWorkspaceSnapshot(localDir: string, includeReposito const branch = (settled[1] as PromiseFulfilledResult).value.stdout.trim(); const snapshot: GitWorkspaceSnapshot = { headCommit: head, branchName: branch && branch !== "HEAD" ? branch : null, + originUrl: await readSanitizedOriginRemoteUrl(localDir), overlayPaths: writer.paths("overlay"), deletedPaths: writer.paths("deleted"), ignoredPaths: writer.paths("ignored"), ...(repositories.length ? { repositories } : {}), }; @@ -573,7 +586,7 @@ export async function readSanitizedOriginRemoteUrl(localDir: string): Promise { +export async function copyCloneTree(source: string, target: string): Promise { await fs.mkdir(target, { recursive: true }); for await (const entry of await fs.opendir(source)) { const from = path.join(source, entry.name); @@ -594,7 +607,7 @@ export async function withShallowGitWorkspaceClone( const cloneDir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-git-workspace-")); const tempRef = `refs/paperclip/git-sync/import/${randomUUID()}`; try { - const originUrl = await readSanitizedOriginRemoteUrl(input.localDir); + const originUrl = input.snapshot.originUrl !== undefined ? input.snapshot.originUrl : await readSanitizedOriginRemoteUrl(input.localDir); await runLocalGit(input.localDir, ["update-ref", tempRef, input.snapshot.headCommit], { timeout: 10_000, maxBuffer: 16 * 1024, diff --git a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts index c099f588a27..c81f162ea32 100644 --- a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts +++ b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts @@ -1059,7 +1059,7 @@ describe("sandbox callback bridge", () => { await bridge.stop(); }); - await writeFile( + await publishBridgeRequest( path.posix.join(directories.requestsDir, "existing.json"), `${JSON.stringify({ id: "existing", @@ -1070,7 +1070,6 @@ describe("sandbox callback bridge", () => { body: "", createdAt: new Date().toISOString(), })}\n`, - "utf8", ); const queueFullResponse = await fetch(`${bridge.baseUrl}/api/agents/me`, { @@ -1900,7 +1899,7 @@ describe("sandbox callback bridge", () => { const queueDir = path.posix.join(rootDir, "queue"); const directories = sandboxCallbackBridgeDirectories(queueDir); await mkdir(directories.requestsDir, { recursive: true }); - await writeFile(path.posix.join(directories.requestsDir, "req-a.json"), bridgeRequestJson("req-a"), "utf8"); + await publishBridgeRequest(path.posix.join(directories.requestsDir, "req-a.json"), bridgeRequestJson("req-a")); const { runtimeSpan, workerErrors } = createWorkerErrorCapture(); @@ -2949,7 +2948,7 @@ describe("sandbox callback bridge", () => { const queueDir = path.posix.join(rootDir, "queue"); const directories = sandboxCallbackBridgeDirectories(queueDir); await mkdir(directories.requestsDir, { recursive: true }); - await writeFile(path.posix.join(directories.requestsDir, "req-w.json"), bridgeRequestJson("req-w"), "utf8"); + await publishBridgeRequest(path.posix.join(directories.requestsDir, "req-w.json"), bridgeRequestJson("req-w")); const base = createFileSystemSandboxCallbackBridgeQueueClient(); let listCalls = 0; @@ -2996,7 +2995,7 @@ describe("sandbox callback bridge", () => { const queueDir = path.posix.join(rootDir, "queue"); const directories = sandboxCallbackBridgeDirectories(queueDir); await mkdir(directories.requestsDir, { recursive: true }); - await writeFile(path.posix.join(directories.requestsDir, "req-ok.json"), bridgeRequestJson("req-ok"), "utf8"); + await publishBridgeRequest(path.posix.join(directories.requestsDir, "req-ok.json"), bridgeRequestJson("req-ok")); const { runtimeSpan, workerErrors } = createWorkerErrorCapture(); const processed: string[] = []; diff --git a/packages/adapter-utils/src/sandbox-managed-deletions.test.ts b/packages/adapter-utils/src/sandbox-managed-deletions.test.ts new file mode 100644 index 00000000000..caeed78cba1 --- /dev/null +++ b/packages/adapter-utils/src/sandbox-managed-deletions.test.ts @@ -0,0 +1,79 @@ +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { promisify } from "node:util"; +import { afterEach, expect, it } from "vitest"; +import { prepareSandboxManagedRuntime, type SandboxManagedRuntimeClient } from "./sandbox-managed-runtime.js"; + +const exec = promisify(execFile); +const roots: string[] = []; +afterEach(async () => { + for (const root of roots.splice(0)) await fs.rm(root, { recursive: true, force: true }); +}); + +async function deletedFixture(names: string[], beforeDelete?: (remote: string) => Promise) { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "sandbox-deletions-")); + roots.push(root); + const source = path.join(root, "source"), remote = path.join(root, "remote"); + await fs.mkdir(source); + const git = (...args: string[]) => exec("git", ["-C", source, ...args]); + await git("init"); + for (const name of names) { + await fs.mkdir(path.dirname(path.join(source, name)), { recursive: true }); + await fs.writeFile(path.join(source, name), "tracked content"); + } + await git("add", "--", "."); + await git("-c", "user.name=Test", "-c", "user.email=test@example.test", "commit", "-qm", "fixture"); + for (const name of names) await fs.rm(path.join(source, name)); + const client: SandboxManagedRuntimeClient = { + makeDir: async directory => { await fs.mkdir(directory, { recursive: true }); }, + writeFile: async (file, bytes) => { await fs.writeFile(file, Buffer.from(bytes)); }, + readFile: file => fs.readFile(file), + listFiles: directory => fs.readdir(directory), + remove: file => fs.rm(file, { recursive: true, force: true }), + run: async command => { await exec("sh", ["-c", command]); }, + syncIn: async operations => { + for (const operation of operations) { + for (const mapping of operation.files) { + await fs.mkdir(path.dirname(mapping.targetPath), { recursive: true }); + await fs.copyFile(mapping.sourcePath, mapping.targetPath); + } + for (const { command } of operation.postUploadCommands ?? []) { + if (command.includes("xargs -0")) await beforeDelete?.(remote); + await exec("sh", ["-c", command]); + } + } + return { operations: [] }; + }, + }; + return { root, remote, prepare: () => prepareSandboxManagedRuntime({ + spec: { transport: "sandbox", provider: "test", sandboxId: "test", remoteCwd: remote, timeoutMs: 30_000, apiKey: null }, + client, adapterKey: "test", workspaceLocalDir: source, + }) }; +} + +it("deletes multiple bounded batches with newline, whitespace, Unicode and leading-dash filenames", async () => { + const names = [" space ", "line\nbreak", "-option", "雪-💾", "wild[?]*", + ...Array.from({ length: 130 }, (_, index) => `nested/file-${index}`)]; + const fixture = await deletedFixture(names); + const runtime = await fixture.prepare(); + try { + for (const name of names) await expect(fs.lstat(path.join(fixture.remote, name))).rejects.toMatchObject({ code: "ENOENT" }); + } finally { await runtime.cleanupWorkspaceSnapshot(); } +}); + +it.each(["symlink", "non_directory"] as const)("rejects a %s ancestor before deleting any entry in its batch", async kind => { + let outside: string; + const fixture = await deletedFixture(["a-valid", "z-parent/secret"], async remote => { + outside = path.join(path.dirname(remote), "outside"); + await fs.mkdir(outside); + await fs.writeFile(path.join(outside, "secret"), "private content"); + await fs.rm(path.join(remote, "z-parent"), { recursive: true }); + if (kind === "symlink") await fs.symlink(outside, path.join(remote, "z-parent")); + else await fs.writeFile(path.join(remote, "z-parent"), "blocker"); + }); + await expect(fixture.prepare()).rejects.toThrow(); + expect(await fs.readFile(path.join(outside!, "secret"), "utf8")).toBe("private content"); + expect(await fs.readFile(path.join(fixture.remote, "a-valid"), "utf8")).toBe("tracked content"); +}); diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts index e77ca65d159..6dc82ebf4d8 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts @@ -631,6 +631,9 @@ describe("sandbox managed runtime", () => { const localAssetsDir = path.join(rootDir, "local-assets"); const linkedAssetPath = path.join(rootDir, "linked-skill.md"); await mkdir(path.join(localWorkspaceDir, ".claude"), { recursive: true }); + const cloneStaging = path.join(".paperclip-runtime", "repository-staging", "pending.clone-abc", "partial.txt"); + await mkdir(path.dirname(path.join(localWorkspaceDir, cloneStaging)), { recursive: true }); + await writeFile(path.join(localWorkspaceDir, cloneStaging), "Incomplete private clone"); await mkdir(localAssetsDir, { recursive: true }); await writeFile(path.join(localWorkspaceDir, "README.md"), "local workspace\n", "utf8"); await writeFile(path.join(localWorkspaceDir, "._README.md"), "appledouble\n", "utf8"); @@ -692,6 +695,7 @@ describe("sandbox managed runtime", () => { }); await expect(readFile(path.join(remoteWorkspaceDir, "README.md"), "utf8")).resolves.toBe("local workspace\n"); + await expect(readFile(path.join(remoteWorkspaceDir, cloneStaging), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); await expect(readFile(path.join(remoteWorkspaceDir, "._README.md"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); await expect(readFile(path.join(remoteWorkspaceDir, ".claude", "settings.json"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); await expect(readFile(path.join(prepared.assetDirs.skills, "skill.md"), "utf8")).resolves.toBe("skill body\n"); @@ -720,6 +724,7 @@ describe("sandbox managed runtime", () => { expect.stringMatching(/^config_sync:Syncing skills to environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/), expect.stringMatching(/^restore:Restoring workspace from environment: 100% \(\d+\.\d\/\d+\.\d MB\)$/), ])); + await expect(readFile(path.join(localWorkspaceDir, cloneStaging), "utf8")).resolves.toBe("Incomplete private clone"); expect(runtimeStatuses.at(-1)).toBe("finalize:Finalizing workspace"); expect(run.mock.calls.some(([command]) => command.includes("workspace-download.tar"))).toBe(true); }); diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.ts b/packages/adapter-utils/src/sandbox-managed-runtime.ts index 0a727973002..a2164603e9f 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.ts @@ -1,3 +1,6 @@ +import type { WorkspaceCheckpointMetrics } from "./workspace-checkpoint.js"; +import { workspaceSeedGeneration, readWorkspaceSeedGeneration, publishWorkspaceSeedGeneration } from "./workspace-seed-cache.js"; +import { writeCheckpointBaseline, workspaceCheckpointScript, readCheckpointSnapshot, validateCheckpointPayload } from "./workspace-checkpoint.js"; import { execFile as execFileCallback } from "node:child_process"; import { createHash, randomUUID } from "node:crypto"; import { @@ -6,7 +9,7 @@ import { promises as fs, } from "node:fs"; import os from "node:os"; -import { workspacePaths, workspacePathMatcher, writeWorkspacePaths, isPathManifest, type WorkspacePaths } from "./workspace-manifest.js"; +import { createWorkspaceManifest, WorkspaceManifestMap, workspacePaths, workspacePathMatcher, writeWorkspacePaths, isPathManifest, type WorkspacePaths } from "./workspace-manifest.js"; import path from "node:path"; import { promisify } from "node:util"; import { @@ -20,6 +23,8 @@ import { type GitWorkspaceSnapshot, integrateImportedGitHead, readGitWorkspaceSnapshot, + readManagedWorkspaceRepositories, + copyCloneTree, disposeGitWorkspaceSnapshot, ReferencedSourceIgnoreScanLimitExceededError, readReferencedSourceGitIgnoredPaths, @@ -29,6 +34,7 @@ import { } from "./git-workspace-sync.js"; import { captureDirectorySnapshot, + directorySnapshotSha256, classifyWorkspaceRestoreFailure, disposeDirectorySnapshot, selectDirectorySnapshot, @@ -362,6 +368,14 @@ function isWorkspaceGitScanSaturatedError(error: unknown): boolean { ); } +function isCheckpointCaptureCapacityError(error: unknown): boolean { + if (!error || typeof error !== "object") return false; + const failure = error as { code?: unknown; message?: unknown; stderr?: unknown }; + if (["ENOSPC", "EDQUOT", "SQLITE_FULL"].includes(String(failure.code))) return true; + const diagnostic = [failure.message, failure.stderr].filter(value => typeof value === "string").join("\n"); + return /Workspace checkpoint storage is below its free-space reserve|\b(?:ENOSPC|EDQUOT|SQLITE_FULL)\b|database or disk is full/i.test(diagnostic); +} + /** * Resolve a referenced project's Git-ignored paths ONCE, before any staging * site runs. Called once per project (see `execute.ts`); the sandbox lane, @@ -606,6 +620,7 @@ export interface PreparedSandboxManagedRuntime { workspaceSyncSnapshot: { baseline: DirectorySnapshot; gitSnapshot: GitWorkspaceSnapshot | null; + repositories?: NonNullable; } | null; cleanupWorkspaceSnapshot(): Promise; restoreWorkspace(onProgress?: RuntimeProgressSink): Promise; @@ -670,6 +685,7 @@ function buildWorkspaceTarExtractCommand(input: { workspaceRemoteDir: string; remoteTar: string; wipeExceptNames: string[] | null; + exclude?: string[]; }): string { // The wipe must also preserve any in-flight sync scratch tarball at the // workspace root. A concurrent referenced-project upload stages a scratch @@ -686,7 +702,7 @@ function buildWorkspaceTarExtractCommand(input: { : ""; return ( `mkdir -p ${shellQuote(input.workspaceRemoteDir)}${wipe} && ` + - `tar -xf ${shellQuote(input.remoteTar)} -C ${shellQuote(input.workspaceRemoteDir)} && ` + + `tar${(input.exclude ?? []).map(entry => ` --exclude=${shellQuote(escapeTarExcludeLiteral(entry))}`).join("")} -xf ${shellQuote(input.remoteTar)} -C ${shellQuote(input.workspaceRemoteDir)} && ` + `rm -f ${shellQuote(input.remoteTar)}` ); } @@ -696,15 +712,14 @@ function buildWorkspaceTarExtractCommand(input: { // already-confined relative paths from the git snapshot. function buildRemoveDeletedPathsCommand(input: { remoteDir: string; manifestPath: string }): string { // NUL input plus bounded xargs batches preserve whitespace and never create - // one argument list for the full snapshot. Refuse symlink ancestors. const remove = `for entry do parent=$entry while [ "\${parent#*/}" != "$parent" ]; do parent=\${parent%/*} if [ -L "$parent" ] || { [ -e "$parent" ] && [ ! -d "$parent" ]; }; then exit 42; fi done - rm -rf -- "$entry" || exit - done`; + done + rm -rf -- "$@"`; return `cd ${shellQuote(input.remoteDir)} && xargs -0 -r -n 64 sh -c ${shellQuote(remove)} sh < ${shellQuote(input.manifestPath)} && rm -f -- ${shellQuote(input.manifestPath)}`; } @@ -820,7 +835,7 @@ async function copyDurableSeedArchive(input: { ) { throw new Error("workspace_durable_seed_digest_mismatch"); } - await fs.copyFile(input.sourcePath, input.targetPath); + await fs.copyFile(input.sourcePath, input.targetPath, fsConstants.COPYFILE_FICLONE); } async function persistDurableSeedArchive(input: { @@ -838,7 +853,7 @@ async function persistDurableSeedArchive(input: { `.${path.basename(input.targetPath)}.${randomUUID()}.tmp`, ); try { - await fs.copyFile(input.sourcePath, temporary); + await fs.copyFile(input.sourcePath, temporary, fsConstants.COPYFILE_FICLONE); await fs.chmod(temporary, 0o600); await fs.rename(temporary, input.targetPath); } finally { @@ -885,9 +900,10 @@ export async function createTarballFromDirectory(input: { async function extractTarballToDirectory(input: { archivePath: string; localDir: string; + exclude?: string[]; }): Promise { await fs.mkdir(input.localDir, { recursive: true }); - await execTar(["-xf", input.archivePath, "-C", input.localDir]); + await execTar([...(input.exclude ?? []).map(entry => `--exclude=${escapeTarExcludeLiteral(entry)}`), "-xf", input.archivePath, "-C", input.localDir]); } async function* walkDirectory(root: string, relative = "", childrenFirst = false): AsyncGenerator { @@ -899,6 +915,28 @@ async function* walkDirectory(root: string, relative = "", childrenFirst = false } } +async function confineFullWorkspaceExport(root: string): Promise { + const unsafe = () => Object.assign(new Error("Unsafe workspace export directory"), { code: "WORKSPACE_RESTORE_UNSAFE_ARCHIVE" }); + if (!(await fs.lstat(root)).isDirectory()) throw unsafe(); + const canonicalRoot = await fs.realpath(root); + const walk = async (directory: string): Promise => { + if (await fs.realpath(directory) !== directory) throw unsafe(); + for await (const entry of await fs.opendir(directory)) { + const entryPath = path.join(directory, entry.name); + const stats = await fs.lstat(entryPath); + if (stats.isDirectory()) await walk(entryPath); + else if (stats.isSymbolicLink()) { + const target = await fs.readlink(entryPath); + const resolved = path.resolve(directory, target); + if (path.isAbsolute(target) || (resolved !== canonicalRoot && !resolved.startsWith(`${canonicalRoot}${path.sep}`))) { + await fs.unlink(entryPath); + } + } else if (!stats.isFile()) throw unsafe(); + } + }; + await walk(canonicalRoot); +} + async function copyWorkspaceEntry(sourceRoot: string, targetRoot: string, relative: string): Promise { const sourcePath = path.join(sourceRoot, relative); const targetPath = path.join(targetRoot, relative); @@ -1136,11 +1174,15 @@ export async function prepareSandboxManagedRuntime(input: { /** Selects authoritative host staging, exact durable-seed replay, or no-overwrite adoption. */ workspaceInboundMode?: WorkspaceInboundMode; workspaceDurableSeed?: WorkspaceDurableSeedPaths; + workspaceCheckpoint?: boolean; + onWorkspaceCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise; + workspaceSeedCacheDirectory?: string; + workspaceSeedCacheCompanyDirectory?: string; /** Durable snapshots supplied when reconstructing an interrupted restore. */ workspaceBaseline?: DirectorySnapshot; workspaceGitSnapshot?: GitWorkspaceSnapshot | null; + workspaceRepositories?: NonNullable; workspaceExclude?: string[]; - /** Plain persistent directories include all files, independent of Git and task cache exclusions. */ workspaceFileMode?: "all"; preserveAbsentOnRestore?: string[]; assets?: SandboxManagedRuntimeAsset[]; @@ -1228,17 +1270,40 @@ export async function prepareSandboxManagedRuntime(input: { // The git enumeration (`git status --ignored`, the HEAD diffs, `ls-files`). // It reads git's own bookkeeping to decide what to include/exclude, so it is // usually fast, but on a large working tree the `--ignored` walk is not free. - const gitSnapshot = syncWorkspace && input.workspaceFileMode !== "all" + const gitSnapshot = syncWorkspace && (input.workspaceGitSnapshot !== undefined || input.workspaceFileMode !== "all") ? input.workspaceGitSnapshot !== undefined ? input.workspaceGitSnapshot : await runStepSpan("snapshot.git", () => readGitWorkspaceSnapshot(input.workspaceLocalDir), ) : null; + const repositories = gitSnapshot ? (gitSnapshot.repositories ?? []) : input.workspaceRepositories ?? (syncWorkspace ? await readManagedWorkspaceRepositories(input.workspaceLocalDir) : []); + const repositoryGitPaths = (children: typeof repositories, prefix = ""): string[] => children.flatMap(repo => { + const relative = prefix ? `${prefix}/${repo.path}` : repo.path; + return [`${relative}/.git`, ...repositoryGitPaths(repo.snapshot.repositories ?? [], relative)]; + }); + const repositoryMetadataExcludes = repositoryGitPaths(repositories); + let managedIgnored: Awaited> | null = null; + let ignoredPaths = input.workspaceFileMode === "all" ? undefined : gitSnapshot?.ignoredPaths; + if (!gitSnapshot && repositories.length) { + managedIgnored = await createWorkspaceManifest(); + for (const repo of repositories) { + if (input.workspaceFileMode !== "all") for (const relative of workspacePaths(repo.snapshot.ignoredPaths)) managedIgnored.add("ignored", `${repo.path}/${relative}`); + for (const relative of workspacePaths(repo.snapshot.deletedPaths)) managedIgnored.add("deleted", `${repo.path}/${relative}`); + } + ignoredPaths = managedIgnored.paths("ignored"); + } + let deletedPaths = gitSnapshot?.deletedPaths ?? managedIgnored?.paths("deleted") ?? []; + let filesystemDeleted: Awaited> | null = null; + managedIgnored?.close(); let baselineSnapshot: DirectorySnapshot | null = null; const cleanupWorkspaceSnapshot = async () => { await disposeDirectorySnapshot(baselineSnapshot); + if (input.workspaceBaseline && input.workspaceBaseline !== baselineSnapshot) await disposeDirectorySnapshot(input.workspaceBaseline); await disposeGitWorkspaceSnapshot(gitSnapshot); + if (!gitSnapshot) for (const repo of repositories) await disposeGitWorkspaceSnapshot(repo.snapshot); + if (managedIgnored) await fs.rm(path.dirname(managedIgnored.filePath), { recursive: true, force: true }); + if (filesystemDeleted) await fs.rm(path.dirname(filesystemDeleted.filePath), { recursive: true, force: true }); }; try { // A selected subfolder has no cloneable Git snapshot, but its parent @@ -1252,20 +1317,22 @@ export async function prepareSandboxManagedRuntime(input: { } const gitIgnoredExcludes = directoryIgnore?.kind === "git" ? directoryIgnore.ignoredPaths : undefined; const workspaceArchiveExclude = mergeExcludes( + [".paperclip-runtime"], + repositoryMetadataExcludes, input.workspaceFileMode === "all" ? [] : SANDBOX_WORKSPACE_HEAVY_DIR_EXCLUDES, - input.workspaceFileMode === "all" ? [] : [...GIT_ARCHIVE_EXCLUDES], + input.workspaceFileMode === "all" && !gitSnapshot ? [] : [...GIT_ARCHIVE_EXCLUDES], input.workspaceExclude, gitIgnoredExcludes, ); const restoreExclude = mergeExcludes( + repositoryMetadataExcludes, input.workspaceFileMode === "all" ? [] : SANDBOX_WORKSPACE_HEAVY_DIR_EXCLUDES, - input.workspaceFileMode === "all" ? [] : [...GIT_ARCHIVE_EXCLUDES], + input.workspaceFileMode === "all" && !gitSnapshot ? [] : [...GIT_ARCHIVE_EXCLUDES], [".paperclip-runtime"], input.preserveAbsentOnRestore, input.workspaceExclude, gitIgnoredExcludes, ); - const repositories = gitSnapshot?.repositories ?? []; const workspaceRestoreExclude = mergeExcludes(restoreExclude, repositories.map((repo) => repo.path)); // The baseline "before" snapshot: a recursive walk of the whole workspace that // `lstat`s every entry and SHA-256-hashes every file's bytes. This is the @@ -1276,11 +1343,33 @@ export async function prepareSandboxManagedRuntime(input: { (await runStepSpan("snapshot.baseline", () => captureDirectorySnapshot(input.workspaceLocalDir, { exclude: restoreExclude, - ignoredPaths: gitSnapshot?.ignoredPaths, + ignoredPaths, diskBacked: true, }), ))) : null; + if (input.workspaceBaseline && repositoryMetadataExcludes.length) { + baselineSnapshot = await selectDirectorySnapshot(input.workspaceBaseline, { + omit: repositoryMetadataExcludes, exclude: mergeExcludes(input.workspaceBaseline.exclude, repositoryMetadataExcludes), + ignoredPaths: input.workspaceBaseline.ignoredPaths, + }); + } + if (input.workspaceFileMode === "all" && baselineSnapshot + && (isPathManifest(deletedPaths) ? deletedPaths.count : deletedPaths.length) > 0) { + filesystemDeleted = await createWorkspaceManifest(); + try { + for (const relative of workspacePaths(deletedPaths)) { + if (!baselineSnapshot.entries.has(relative)) filesystemDeleted.add("deleted", relative); + } + deletedPaths = filesystemDeleted.paths("deleted"); + } finally { filesystemDeleted.close(); } + } + + const seedGeneration = input.workspaceSeedCacheDirectory && baselineSnapshot && workspaceInboundMode !== "durable_seed" + ? workspaceSeedGeneration(baselineSnapshot, gitSnapshot, repositories) : null; + const cachedSeed = seedGeneration + ? await readWorkspaceSeedGeneration(input.workspaceSeedCacheDirectory!, seedGeneration) : null; + const replaySeed = workspaceInboundMode === "durable_seed" ? input.workspaceDurableSeed : cachedSeed; // Every inbound staging step delegates to the provider through `client.syncIn`: // the orchestrator no longer inlines `writeFile`+`run` or chooses a transport, @@ -1430,38 +1519,30 @@ export async function prepareSandboxManagedRuntime(input: { // `runtimeRootDir` (`.paperclip-runtime/`). The git extract // wipes the target tree EXCEPT `.paperclip-runtime`, so the overlay tar, // which sits under `.paperclip-runtime`, survives to run its own extract. - if (gitSnapshot) { + if (gitSnapshot || repositories.length) { await emitRuntimeStatus(input.onRuntimeProgress, "git_sync", "Syncing git history to environment"); const gitTarPath = path.join(tempDir, "git-workspace.tar"); const remoteGitTar = path.posix.join( runtimeRootDir, "git-workspace-upload.tar", ); - if (workspaceInboundMode === "durable_seed") { - const durableGitArchive = - input.workspaceDurableSeed?.gitArchivePath; + if (replaySeed) { + const durableGitArchive = replaySeed.gitArchivePath; if (!durableGitArchive) { throw new Error("workspace_durable_seed_git_missing"); } await copyDurableSeedArchive({ sourcePath: durableGitArchive, targetPath: gitTarPath, - expectedSha256: input.workspaceDurableSeed?.gitArchiveSha256, + expectedSha256: replaySeed.gitArchiveSha256, }); } else { - await withShallowGitWorkspaceClone( - { - localDir: input.workspaceLocalDir, - snapshot: gitSnapshot, - }, - async (cloneDir) => { - await createTarballFromDirectory({ - localDir: cloneDir, - archivePath: gitTarPath, - exclude: [".paperclip-runtime"], - }); - }, - ); + const archive = async (cloneDir: string) => createTarballFromDirectory({ localDir: cloneDir, archivePath: gitTarPath, exclude: [".paperclip-runtime"] }); + if (gitSnapshot) await withShallowGitWorkspaceClone({ localDir: input.workspaceLocalDir, snapshot: gitSnapshot }, archive); + else await withTempDir("paperclip-managed-repositories-", async (cloneDir) => { + for (const repo of repositories) await withShallowGitWorkspaceClone({ localDir: path.join(input.workspaceLocalDir, repo.path), snapshot: repo.snapshot }, async (child) => copyCloneTree(child, path.join(cloneDir, repo.path))); + await archive(cloneDir); + }); if (input.workspaceDurableSeed?.gitArchivePath) { await persistDurableSeedArchive({ sourcePath: gitTarPath, @@ -1469,6 +1550,7 @@ export async function prepareSandboxManagedRuntime(input: { }); } } + if (cachedSeed && input.workspaceDurableSeed?.gitArchivePath) await persistDurableSeedArchive({ sourcePath: gitTarPath, targetPath: input.workspaceDurableSeed.gitArchivePath }); workspaceFiles.push({ sourcePath: gitTarPath, targetPath: remoteGitTar, @@ -1491,30 +1573,30 @@ export async function prepareSandboxManagedRuntime(input: { // the preserved names first. The extract runs AFTER the git extract. await emitRuntimeStatus(input.onRuntimeProgress, "config_sync", "Syncing workspace to environment"); const workspaceTarPath = path.join(tempDir, "workspace.tar"); - if (workspaceInboundMode === "durable_seed") { + if (replaySeed) { await copyDurableSeedArchive({ - sourcePath: input.workspaceDurableSeed!.workspaceArchivePath, + sourcePath: replaySeed.workspaceArchivePath, targetPath: workspaceTarPath, expectedSha256: - input.workspaceDurableSeed!.workspaceArchiveSha256, + replaySeed.workspaceArchiveSha256, }); } else { - const workspaceArchiveDir = gitSnapshot + const workspaceArchiveDir = gitSnapshot || repositories.length ? path.join(tempDir, "workspace-overlay") : input.workspaceLocalDir; - if (gitSnapshot) { + if (gitSnapshot || repositories.length) { await copySelectedWorkspaceEntries({ sourceRoot: workspaceRoot!, targetDir: workspaceArchiveDir, - relativePaths: gitSnapshot.overlayPaths, - ignoredPaths: gitSnapshot.ignoredPaths, + relativePaths: (input.workspaceFileMode === "all" ? undefined : gitSnapshot?.overlayPaths) ?? (baselineSnapshot!.entries instanceof WorkspaceManifestMap ? baselineSnapshot!.entries.manifest : [...baselineSnapshot!.entries.keys()]), + ignoredPaths, exclude: workspaceArchiveExclude, }); } await createTarballFromDirectory({ localDir: workspaceArchiveDir, archivePath: workspaceTarPath, - exclude: gitSnapshot ? undefined : workspaceArchiveExclude, + exclude: gitSnapshot || repositories.length ? undefined : workspaceArchiveExclude, }); if (input.workspaceDurableSeed) { await persistDurableSeedArchive({ @@ -1523,6 +1605,7 @@ export async function prepareSandboxManagedRuntime(input: { }); } } + if (cachedSeed && input.workspaceDurableSeed) await persistDurableSeedArchive({ sourcePath: workspaceTarPath, targetPath: input.workspaceDurableSeed.workspaceArchivePath }); const remoteWorkspaceTar = path.posix.join( runtimeRootDir, "workspace-upload.tar", @@ -1538,20 +1621,47 @@ export async function prepareSandboxManagedRuntime(input: { command: buildWorkspaceTarExtractCommand({ workspaceRemoteDir, remoteTar: remoteWorkspaceTar, - wipeExceptNames: gitSnapshot ? null : [...preservedNames], + wipeExceptNames: gitSnapshot || repositories.length ? null : [...preservedNames], + exclude: repositoryMetadataExcludes, }), }); // 3. Optional remove-deleted-paths command runs LAST, after both extracts. - if (gitSnapshot && (isPathManifest(gitSnapshot.deletedPaths) ? gitSnapshot.deletedPaths.count : gitSnapshot.deletedPaths.length) > 0) { + if ((isPathManifest(deletedPaths) ? deletedPaths.count : deletedPaths.length) > 0) { const deletedManifest = path.join(tempDir, "deleted.nul"); const remoteDeletedManifest = path.posix.join(runtimeRootDir, "deleted.nul"); - await writeWorkspacePaths(gitSnapshot.deletedPaths, deletedManifest); + await writeWorkspacePaths(deletedPaths, deletedManifest); workspaceFiles.push({ sourcePath: deletedManifest, targetPath: remoteDeletedManifest, kind: "file", access: "rw", writablePath: runtimeRootDir }); workspacePostUploadCommands.push({ command: buildRemoveDeletedPathsCommand({ remoteDir: workspaceRemoteDir, manifestPath: remoteDeletedManifest }) }); } workspaceUploadBytes += (await fs.stat(workspaceTarPath)).size; }); + if (seedGeneration && !cachedSeed && input.workspaceDurableSeed) { + const seed = input.workspaceDurableSeed; + await publishWorkspaceSeedGeneration(input.workspaceSeedCacheDirectory!, seedGeneration, + { ...seed, gitArchivePath: gitSnapshot || repositories.length ? seed.gitArchivePath : null }, { + companyDirectory: input.workspaceSeedCacheCompanyDirectory, + verify: () => runStepSpan("seed.verify", async () => { + const verifiedRoot = path.join(tempDir, "verify-seed"); + try { + if (gitSnapshot || repositories.length) await extractTarballToDirectory({ archivePath: path.join(tempDir, "git-workspace.tar"), localDir: verifiedRoot }); + await extractTarballToDirectory({ archivePath: path.join(tempDir, "workspace.tar"), localDir: verifiedRoot, exclude: repositoryMetadataExcludes }); + for (const relative of workspacePaths(deletedPaths)) { + let parent = verifiedRoot; + for (const segment of relative.split("/").slice(0, -1)) { + parent = path.join(parent, segment); + const stat = await fs.lstat(parent).catch((error: NodeJS.ErrnoException) => { if (error.code === "ENOENT") return null; throw error; }); + if (stat && !stat.isDirectory()) return false; + } + await fs.rm(path.join(verifiedRoot, relative), { recursive: true, force: true }); + } + const verified = await captureDirectorySnapshot(verifiedRoot, { exclude: baselineSnapshot!.exclude, ignoredPaths: baselineSnapshot!.ignoredPaths, diskBacked: true }); + try { return directorySnapshotSha256(verified) === directorySnapshotSha256(baselineSnapshot!); } + finally { await disposeDirectorySnapshot(verified); } + } finally { await fs.rm(verifiedRoot, { recursive: true, force: true }); } + }), + }); + } if (!stageWorkspace) return; // One confined `syncIn` for the whole merged workspace file set. The confine @@ -1748,7 +1858,7 @@ export async function prepareSandboxManagedRuntime(input: { additionalSourceFailures, workspaceSyncSnapshot: syncWorkspace && baselineSnapshot - ? { baseline: baselineSnapshot, gitSnapshot } + ? { baseline: baselineSnapshot, gitSnapshot, ...(!gitSnapshot && repositories.length ? { repositories } : {}) } : null, cleanupWorkspaceSnapshot, restoreWorkspace: async (onProgress?: RuntimeProgressSink) => { @@ -1798,12 +1908,15 @@ export async function prepareSandboxManagedRuntime(input: { adapterKey: input.adapterKey, workspaceLocalDir: localDir, workspaceInboundMode: "adopt_remote", + workspaceCheckpoint: input.workspaceCheckpoint, + onWorkspaceCheckpoint: input.onWorkspaceCheckpoint, workspaceGitSnapshot: repository.snapshot, + workspaceFileMode: input.workspaceFileMode, workspaceExclude: nestedExclude, workspaceBaseline: await selectDirectorySnapshot(baselineSnapshot!, { prefix, - exclude: mergeExcludes(SANDBOX_WORKSPACE_HEAVY_DIR_EXCLUDES, [...GIT_ARCHIVE_EXCLUDES], [".paperclip-runtime"], nestedExclude), - ignoredPaths: repository.snapshot.ignoredPaths, + exclude: mergeExcludes(input.workspaceFileMode === "all" ? [] : SANDBOX_WORKSPACE_HEAVY_DIR_EXCLUDES, [...GIT_ARCHIVE_EXCLUDES], [".paperclip-runtime"], nestedExclude), + ignoredPaths: input.workspaceFileMode === "all" ? undefined : repository.snapshot.ignoredPaths, }), onRuntimeProgress: input.onRuntimeProgress, workspaceRestoreRetryDelaysMs: input.workspaceRestoreRetryDelaysMs, @@ -1816,6 +1929,7 @@ export async function prepareSandboxManagedRuntime(input: { let importedRef: string | null = null; let importedHead: string | null = null; let remoteWorkspaceStatus = "dirty"; + let checkpointSnapshot: DirectorySnapshot | undefined; try { if (gitSnapshot) { await emitRuntimeStatus(input.onRuntimeProgress, "export", "Exporting git changes from environment"); @@ -1908,7 +2022,60 @@ export async function prepareSandboxManagedRuntime(input: { await emitRuntimeStatus(input.onRuntimeProgress, "restore", "Restoring workspace from environment"); const extractedDir = path.join(tempDir, "workspace"); - if (nativeSyncOut) { + let sparseSupported = false; + if (input.workspaceCheckpoint && nativeSyncOut) { + sparseSupported = await input.client.run( + `node --input-type=module -e ${shellQuote("import {DatabaseSync} from 'node:sqlite'; new DatabaseSync(':memory:').close()")}`, + { timeoutMs: 15_000 }, + ).then(() => true, () => false); + } + if (sparseSupported) { + const checkpointBaseline = repositories.length === 0 ? baselineSnapshot! : await selectDirectorySnapshot(baselineSnapshot!, { + omit: repositories.map((repo) => repo.path), exclude: workspaceRestoreExclude, ignoredPaths, + }); + try { + const inputManifest = path.join(tempDir, "checkpoint-baseline.sqlite"); + const helper = path.join(tempDir, "checkpoint.mjs"); + await writeCheckpointBaseline(checkpointBaseline, inputManifest); + await fs.writeFile(helper, workspaceCheckpointScript()); + const remoteRoot = path.posix.join(runtimeRootDir, `checkpoint-${randomUUID()}`); + const remoteManifest = path.posix.join(remoteRoot, "baseline.sqlite"); + const remoteHelper = path.posix.join(remoteRoot, "checkpoint.mjs"); + const remoteOutput = path.posix.join(remoteRoot, "output"); + const inbound: SandboxSyncOperation[] = [{ operationId: nextSyncOperationId(), files: [ + { sourcePath: inputManifest, targetPath: remoteManifest, kind: "file" }, + { sourcePath: helper, targetPath: remoteHelper, kind: "file" }, + ] }]; + assertSyncOperationsConfined(inbound, { sourceRoots: [tempDir], targetRoots: [runtimeRootDir] }); + await input.client.syncIn!(inbound); + try { + let captured = false; + try { + await input.client.run(["node", remoteHelper, workspaceRemoteDir, remoteManifest, remoteOutput, JSON.stringify(workspaceRestoreExclude)].map(shellQuote).join(" "), { timeoutMs: input.spec.timeoutMs }); + captured = true; + } catch (error) { + if (!isCheckpointCaptureCapacityError(error)) throw error; + } + if (captured) { + const output = path.join(tempDir, "checkpoint"); + const outbound: SandboxSyncOperation[] = [{ operationId: nextSyncOperationId(), files: [ + { sourcePath: remoteOutput, targetPath: output, kind: "directory" }, + ] }]; + assertSyncOperationsConfined(outbound, { sourceRoots: [runtimeRootDir], targetRoots: [tempDir] }); + await withWorkspaceRestoreStep("workspace_transfer", () => input.client.syncOut!(outbound)); + checkpointSnapshot = readCheckpointSnapshot(path.join(output, "manifest.sqlite"), checkpointBaseline); + await fs.rename(path.join(output, "payload"), extractedDir); + const metrics = await validateCheckpointPayload({ baseline: checkpointBaseline, snapshot: checkpointSnapshot, payload: extractedDir }); + await input.onWorkspaceCheckpoint?.(metrics).catch(() => undefined); + } + } catch (error) { + if (error instanceof Error && error.message.includes("Unsafe workspace archive symlink")) Object.assign(error, { code: "WORKSPACE_RESTORE_UNSAFE_ARCHIVE" }); + throw error; + } finally { await input.client.remove(remoteRoot).catch(() => undefined); } + } finally { if (checkpointBaseline !== baselineSnapshot) await disposeDirectorySnapshot(checkpointBaseline); } + } + if (input.workspaceCheckpoint && !checkpointSnapshot) await input.onWorkspaceCheckpoint?.({ mode: "full", scannedEntries: null, changedFiles: null, payloadBytes: null }).catch(() => undefined); + if (!checkpointSnapshot && nativeSyncOut) { // Native outbound: the provider materializes the sandbox workspace into // a fresh host directory. It is a clean destroy-then-replace into a // temp dir the orchestrator just created, so it maps exactly to a @@ -1939,7 +2106,7 @@ export async function prepareSandboxManagedRuntime(input: { const syncResult = await withWorkspaceRestoreStep("workspace_transfer", () => input.client.syncOut!(operations)); const transferredBytes = sumSyncResultBytes(syncResult); await workspaceRestore.finish(transferredBytes, transferredBytes); - } else { + } else if (!checkpointSnapshot) { const remoteWorkspaceTar = path.posix.join(runtimeRootDir, "workspace-download.tar"); await withWorkspaceRestoreStep("workspace_transfer", () => input.client.run( `sh -c ${shellQuote(createRemoteTarballFromDirectoryCommand({ @@ -1969,15 +2136,17 @@ export async function prepareSandboxManagedRuntime(input: { }); }); } + if (!checkpointSnapshot) await confineFullWorkspaceExport(extractedDir); const gitHeadToIntegrate = importedHead; await withWorkspaceRestoreStep("directory_merge", async () => { const mergeBaseline = repositories.length === 0 ? baselineSnapshot! : await selectDirectorySnapshot(baselineSnapshot!, { - omit: repositories.map((repo) => repo.path), exclude: workspaceRestoreExclude, ignoredPaths: gitSnapshot?.ignoredPaths, + omit: repositories.map((repo) => repo.path), exclude: workspaceRestoreExclude, ignoredPaths, }); try { await mergeDirectoryWithBaseline({ baseline: mergeBaseline, sourceDir: extractedDir, + ...(checkpointSnapshot ? { snapshots: { source: checkpointSnapshot } } : {}), targetDir: input.workspaceLocalDir, // Runs under the merge lock, before the first host write. beforeApply: async () => { @@ -2001,6 +2170,7 @@ export async function prepareSandboxManagedRuntime(input: { } finally { if (mergeBaseline !== baselineSnapshot) await disposeDirectorySnapshot(mergeBaseline); } }); } finally { + await disposeDirectorySnapshot(checkpointSnapshot ?? null); await emitRuntimeStatus(input.onRuntimeProgress, "finalize", "Finalizing workspace"); if (importedRef) { await withWorkspaceRestoreStep("git_ref_cleanup", () => deleteLocalGitRef({ localDir: input.workspaceLocalDir, ref: importedRef! })); diff --git a/packages/adapter-utils/src/sandbox-managed-seed.test.ts b/packages/adapter-utils/src/sandbox-managed-seed.test.ts new file mode 100644 index 00000000000..e54bdc7d8e7 --- /dev/null +++ b/packages/adapter-utils/src/sandbox-managed-seed.test.ts @@ -0,0 +1,113 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { afterEach, expect, it, vi } from "vitest"; +import { createTarballFromDirectory, prepareSandboxManagedRuntime, type SandboxSyncOperation } from "./sandbox-managed-runtime.js"; +import { captureDirectorySnapshot } from "./workspace-restore-merge.js"; + +const execute = promisify(execFile); +const roots: string[] = []; +afterEach(async () => { + vi.unstubAllEnvs(); + await Promise.all(roots.splice(0).map(root => fs.rm(root, { recursive: true, force: true }))); +}); + +it.each([undefined, "all"] as const)("keeps nested Git metadata sanitized in cold, cached and recovered seeds (file mode %s)", async (workspaceFileMode) => { + const temp = await fs.mkdtemp(path.join(os.tmpdir(), "managed-seed-sanitized-")); roots.push(temp); + vi.stubEnv("PAPERCLIP_HOME", path.join(temp, "home")); + const host = path.join(temp, "host"), remote = path.join(temp, "remote"), cache = path.join(temp, "cache"); + const relative = ".paperclip-repositories/fixture"; + const repository = path.join(host, relative); await fs.mkdir(repository, { recursive: true }); + const git = async (root: string, ...args: string[]) => (await execute("git", ["-C", root, ...args])).stdout.trim(); + await git(repository, "init", "-b", "main"); + await fs.writeFile(path.join(repository, ".gitignore"), "ignored.txt\n"); + await fs.writeFile(path.join(repository, "work.txt"), "first\n"); + if (workspaceFileMode === "all") { + await fs.writeFile(path.join(repository, "notes.txt"), "keep after untracking\n"); + await fs.writeFile(path.join(repository, "absent.txt"), "truly deleted\n"); + } + await git(repository, "add", "."); + await git(repository, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "first"); + await git(repository, "branch", "private-fixture"); + await fs.writeFile(path.join(repository, "work.txt"), "committed\n"); + await git(repository, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-am", "selected"); + const head = await git(repository, "rev-parse", "HEAD"); + await git(repository, "remote", "add", "origin", "https://fixture-user:fixture-token@example.test/repository.git"); + await fs.writeFile(path.join(repository, ".git/hooks/post-commit"), "private-hook-fixture"); + await fs.writeFile(path.join(repository, "work.txt"), "dirty\n"); + await fs.writeFile(path.join(repository, "ignored.txt"), "ordinary ignored bytes"); + if (workspaceFileMode === "all") { + await git(repository, "rm", "--cached", "notes.txt"); + await fs.unlink(path.join(repository, "absent.txt")); + } + const run = async (command: string) => { await execute("sh", ["-c", command]); }; + const sync = async (operations: SandboxSyncOperation[]) => { + for (const operation of operations) { + for (const file of operation.files) { + await fs.mkdir(path.dirname(file.targetPath), { recursive: true }); + await fs.cp(file.sourcePath, file.targetPath, { recursive: true, verbatimSymlinks: true }); + if (file.sourcePath.endsWith("/workspace.tar")) { + await expect(fs.stat(path.join(path.dirname(file.sourcePath), "verify-seed"))).rejects.toMatchObject({ code: "ENOENT" }); + } + } + for (const command of operation.postUploadCommands ?? []) await run(command.command); + } + return { operations: operations.map(operation => ({ operationId: operation.operationId, filesTransferred: 0, bytesTransferred: 0 })) }; + }; + const client = { makeDir: (dir: string) => fs.mkdir(dir, { recursive: true }).then(() => {}), run, + writeFile: (file: string, data: ArrayBuffer) => fs.writeFile(file, Buffer.from(data)), readFile: (file: string) => fs.readFile(file), + listFiles: (dir: string) => fs.readdir(dir), remove: (file: string) => fs.rm(file, { force: true, recursive: true }), syncIn: sync, syncOut: sync }; + const seed = { workspaceArchivePath: path.join(temp, "run-workspace.tar"), gitArchivePath: path.join(temp, "run-git.tar") }; + let verifications = 0; + const prepare = (overrides: Partial[0]> = {}) => prepareSandboxManagedRuntime({ + spec: { provider: "test", sandboxId: "test", remoteCwd: remote, timeoutMs: 30000, apiKey: null, transport: "sandbox" }, + adapterKey: "test", client, workspaceLocalDir: host, workspaceFileMode, workspaceSeedCacheDirectory: cache, + workspaceDurableSeed: seed, runtimeSpan: async (name, work) => { if (name === "seed.verify") verifications++; return work(); }, ...overrides, + }); + const assertSanitized = async () => { + const checkout = path.join(remote, relative); + expect(await git(checkout, "remote", "get-url", "origin")).toBe("https://example.test/repository.git"); + expect(await fs.readFile(path.join(checkout, ".git/config"), "utf8")).not.toContain("fixture-token"); + await expect(fs.stat(path.join(checkout, ".git/hooks/post-commit"))).rejects.toMatchObject({ code: "ENOENT" }); + await expect(git(checkout, "rev-parse", "--verify", "refs/heads/private-fixture")).rejects.toThrow(); + expect(await git(checkout, "rev-parse", "HEAD")).toBe(head); + expect(await git(checkout, "show", "HEAD:work.txt")).toBe("committed"); + expect(await fs.readFile(path.join(checkout, "work.txt"), "utf8")).toBe("dirty\n"); + if (workspaceFileMode === "all") { + expect(await fs.readFile(path.join(checkout, "ignored.txt"), "utf8")).toBe("ordinary ignored bytes"); + expect(await fs.readFile(path.join(checkout, "notes.txt"), "utf8")).toBe("keep after untracking\n"); + await expect(fs.stat(path.join(checkout, "absent.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + } + }; + const cold = await prepare(); await assertSanitized(); + expect([...cold.workspaceSyncSnapshot!.baseline.entries].some(([key]) => key.startsWith(`${relative}/.git/`))).toBe(false); + expect(await fs.readdir(cache)).toHaveLength(1); expect(verifications).toBe(1); + await cold.cleanupWorkspaceSnapshot(); + await fs.writeFile(path.join(repository, ".git/hooks/post-commit"), "changed private hook"); + const cached = await prepare(); await assertSanitized(); expect(verifications).toBe(1); + const oldBaseline = await captureDirectorySnapshot(host, { diskBacked: true }); + await createTarballFromDirectory({ localDir: host, archivePath: seed.workspaceArchivePath }); + const recovered = await prepare({ workspaceInboundMode: "durable_seed", workspaceBaseline: oldBaseline, + workspaceGitSnapshot: null, workspaceRepositories: cached.workspaceSyncSnapshot!.repositories }); + await assertSanitized(); + expect([...recovered.workspaceSyncSnapshot!.baseline.entries].some(([key]) => key.startsWith(`${relative}/.git/`))).toBe(false); + const remoteRepository = path.join(remote, relative); + await fs.writeFile(path.join(remoteRepository, "work.txt"), "remote commit\n"); + await git(remoteRepository, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-am", "remote commit"); + const remoteHead = await git(remoteRepository, "rev-parse", "HEAD"); + await fs.writeFile(path.join(remoteRepository, "work.txt"), "restored dirty bytes\n"); + if (workspaceFileMode === "all") await fs.writeFile(path.join(remoteRepository, "ignored.txt"), "updated ordinary ignored bytes"); + await recovered.restoreWorkspace(); + expect(await git(repository, "rev-parse", "HEAD")).toBe(remoteHead); + expect(await fs.readFile(path.join(repository, "work.txt"), "utf8")).toBe("restored dirty bytes\n"); + expect(await fs.readFile(path.join(repository, ".git/config"), "utf8")).toContain("fixture-token"); + expect(await fs.readFile(path.join(repository, ".git/hooks/post-commit"), "utf8")).toBe("changed private hook"); + if (workspaceFileMode === "all") { + expect(await fs.readFile(path.join(repository, "ignored.txt"), "utf8")).toBe("updated ordinary ignored bytes"); + expect(await fs.readFile(path.join(repository, "notes.txt"), "utf8")).toBe("keep after untracking\n"); + await expect(fs.stat(path.join(repository, "absent.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + } + await recovered.cleanupWorkspaceSnapshot(); await cached.cleanupWorkspaceSnapshot(); +}, 30000); diff --git a/packages/adapter-utils/src/ssh.ts b/packages/adapter-utils/src/ssh.ts index 68e61fc6390..ddb07d6ac7b 100644 --- a/packages/adapter-utils/src/ssh.ts +++ b/packages/adapter-utils/src/ssh.ts @@ -1090,7 +1090,7 @@ async function isLocalGitRepositoryRoot(localDir: string): Promise { } } -async function listLocalProjectRepositories(localDir: string): Promise { +export async function listLocalProjectRepositories(localDir: string): Promise { const root = path.join(localDir, PROJECT_REPOSITORIES_DIR); const rootStat = await fs.lstat(root).catch((error: NodeJS.ErrnoException) => { if (error.code === "ENOENT") return null; diff --git a/packages/adapter-utils/src/workspace-checkpoint.test.ts b/packages/adapter-utils/src/workspace-checkpoint.test.ts new file mode 100644 index 00000000000..064cfa6eeae --- /dev/null +++ b/packages/adapter-utils/src/workspace-checkpoint.test.ts @@ -0,0 +1,455 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { captureDirectorySnapshot, disposeDirectorySnapshot, mergeDirectoryWithBaseline } from "./workspace-restore-merge.js"; +import { writeCheckpointBaseline, workspaceCheckpointScript, readCheckpointSnapshot, validateCheckpointPayload } from "./workspace-checkpoint.js"; +import { publishWorkspaceSeedGeneration, readWorkspaceSeedGeneration, workspaceSeedGeneration } from "./workspace-seed-cache.js"; +import { WorkspaceManifestWriter } from "./workspace-manifest.js"; +const execute = promisify(execFile); +const roots: string[] = []; +afterEach(async () => { vi.unstubAllEnvs(); await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); }); +async function fixture() { + const temp = await fs.mkdtemp(path.join(os.tmpdir(), "checkpoint-test-")); roots.push(temp); vi.stubEnv("PAPERCLIP_HOME", path.join(temp, "home")); + const root = path.join(temp, "root"); await fs.mkdir(root); + await fs.writeFile(path.join(root, "unchanged"), "retained"); + await fs.writeFile(path.join(root, "deleted"), "delete me"); + await fs.mkdir(path.join(root, "cache")); await fs.writeFile(path.join(root, "cache", "ignored"), "private"); + const baseline = await captureDirectorySnapshot(root, { diskBacked: true, exclude: ["cache"] }); + const manifest = path.join(temp, "baseline.sqlite"); await writeCheckpointBaseline(baseline, manifest); + const script = path.join(temp, "checkpoint.mjs"); await fs.writeFile(script, workspaceCheckpointScript()); + const capture = async () => { + const output = path.join(temp, "output"); + await execute(process.execPath, [script, root, manifest, output, JSON.stringify(baseline.exclude)]); + const snapshot = readCheckpointSnapshot(path.join(output, "manifest.sqlite"), baseline); + const payload = path.join(output, "payload"); + const metrics = await validateCheckpointPayload({ baseline, snapshot, payload }); + return { snapshot, payload, metrics, receipt: JSON.parse(await fs.readFile(path.join(output, "receipt.json"), "utf8")) }; + }; + return { temp, root, baseline, capture }; +} +describe("workspace sparse checkpoint", () => { + it.each((["file", "symlink", "missing"] as const).flatMap(attack => + (["a/kept", "a/nested/kept"] as const).flatMap(child => + (["manifest", "memory"] as const).map(storage => ({ attack, child, storage })))))( + "rejects a returned $attack ancestor of $child ($storage) before any host mutation", async ({ attack, child, storage }) => { + const f = await fixture(); await disposeDirectorySnapshot(f.baseline); + await fs.mkdir(path.dirname(path.join(f.root, child)), { recursive: true }); + await fs.writeFile(path.join(f.root, child), "retained child"); + const baseline = await captureDirectorySnapshot(f.root, { diskBacked: true, exclude: ["cache"] }); + const payload = path.join(f.temp, "forged-payload"); await fs.mkdir(payload); + await fs.writeFile(path.join(payload, "unchanged"), "otherwise valid edit"); + if (attack === "file") await fs.writeFile(path.join(payload, "a"), "replace directory"); + if (attack === "symlink") await fs.symlink("unchanged", path.join(payload, "a")); + const captured = await captureDirectorySnapshot(payload); + const entries = new Map(baseline.entries); + entries.delete("deleted"); entries.delete("a"); + for (const [relative, entry] of captured.entries) entries.set(relative, entry); + const manifest = path.join(f.temp, "forged.sqlite"); + await writeCheckpointBaseline({ ...baseline, entries }, manifest); + const evidence = await fs.readFile(manifest); + const snapshot = storage === "manifest" ? readCheckpointSnapshot(manifest, baseline) + : { ...baseline, entries: new Map([...entries].reverse()) }; + let failure: unknown; + try { + await validateCheckpointPayload({ baseline, snapshot, payload }); + await mergeDirectoryWithBaseline({ baseline, sourceDir: payload, targetDir: f.root, snapshots: { source: snapshot } }); + } catch (error) { failure = error; } + expect(await fs.readFile(path.join(f.root, child), "utf8")).toBe("retained child"); + expect(await fs.readFile(path.join(f.root, "unchanged"), "utf8")).toBe("retained"); + expect(await fs.readFile(path.join(f.root, "deleted"), "utf8")).toBe("delete me"); + expect(failure).toBeInstanceOf(Error); + expect(String(failure)).toContain("Invalid workspace checkpoint parent"); + expect(await fs.readFile(manifest)).toEqual(evidence); + const replay = readCheckpointSnapshot(manifest, baseline); + await expect(validateCheckpointPayload({ baseline, snapshot: replay, payload })).rejects.toThrow("Invalid workspace checkpoint parent"); + await disposeDirectorySnapshot(replay); await disposeDirectorySnapshot(snapshot); await disposeDirectorySnapshot(baseline); + }); + it.each(["file_to_directory", "directory_to_file"] as const)("restores a coherent %s checkpoint", async transition => { + const f = await fixture(); await disposeDirectorySnapshot(f.baseline); + if (transition === "file_to_directory") await fs.writeFile(path.join(f.root, "a"), "old file"); + else { + await fs.mkdir(path.join(f.root, "a")); await fs.writeFile(path.join(f.root, "a", "old"), "old child"); + } + const baseline = await captureDirectorySnapshot(f.root, { diskBacked: true, exclude: ["cache"] }); + const host = path.join(f.temp, "host"); await fs.cp(f.root, host, { recursive: true }); + await fs.rm(path.join(f.root, "a"), { recursive: true }); + if (transition === "file_to_directory") { + await fs.mkdir(path.join(f.root, "a", "nested"), { recursive: true }); + await fs.writeFile(path.join(f.root, "a", "nested", "new"), "new child"); + } else await fs.writeFile(path.join(f.root, "a"), "new file"); + const manifest = path.join(f.temp, "transition-baseline.sqlite"); await writeCheckpointBaseline(baseline, manifest); + const output = path.join(f.temp, "transition-output"); + await execute(process.execPath, [path.join(f.temp, "checkpoint.mjs"), f.root, manifest, output, JSON.stringify(baseline.exclude)]); + const snapshot = readCheckpointSnapshot(path.join(output, "manifest.sqlite"), baseline), payload = path.join(output, "payload"); + await validateCheckpointPayload({ baseline, snapshot, payload }); + await mergeDirectoryWithBaseline({ baseline, sourceDir: payload, targetDir: host, snapshots: { source: snapshot } }); + expect(await fs.readFile(path.join(host, transition === "file_to_directory" ? "a/nested/new" : "a"), "utf8")) + .toBe(transition === "file_to_directory" ? "new child" : "new file"); + expect(await fs.readFile(path.join(host, "unchanged"), "utf8")).toBe("retained"); + await disposeDirectorySnapshot(snapshot); await disposeDirectorySnapshot(baseline); + }); + it("enumerates unchanged files but transfers zero workspace payload", async () => { + const f = await fixture(); const result = await f.capture(); + expect(result.receipt).toMatchObject({ files: 0, bytes: 0, scanned: 2 }); + expect(result.metrics).toEqual({ mode: "sparse", scannedEntries: 2, changedFiles: 0, payloadBytes: 0 }); + expect(await fs.readdir(result.payload)).toEqual([]); + expect([...result.snapshot.entries]).toEqual([...f.baseline.entries]); + await disposeDirectorySnapshot(result.snapshot); await disposeDirectorySnapshot(f.baseline); + }); + it("merges one changed file, deletion, unusual names and a safe link without copying unchanged files", async () => { + const f = await fixture(); const host = path.join(f.temp, "host"); await fs.cp(f.root, host, { recursive: true }); + const name = "new\nfile"; await fs.writeFile(path.join(f.root, name), "changed"); + await fs.unlink(path.join(f.root, "deleted")); await fs.symlink(name, path.join(f.root, "link")); + const result = await f.capture(); expect(result.receipt.files).toBe(1); expect(result.receipt.bytes).toBe(7); + await mergeDirectoryWithBaseline({ baseline: f.baseline, sourceDir: result.payload, targetDir: host, snapshots: { source: result.snapshot } }); + expect(await fs.readFile(path.join(host, name), "utf8")).toBe("changed"); + expect(await fs.readFile(path.join(host, "unchanged"), "utf8")).toBe("retained"); + expect(await fs.readlink(path.join(host, "link"))).toBe(name); + await expect(fs.stat(path.join(host, "deleted"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await fs.readFile(path.join(host, "cache", "ignored"), "utf8")).toBe("private"); + await disposeDirectorySnapshot(f.baseline); + }); + it.each(["absolute", "escaping"])("omits an %s link while restoring ordinary changes and safe links", async (kind) => { + const f = await fixture(); + const host = path.join(f.temp, "host"); await fs.cp(f.root, host, { recursive: true }); + const outside = path.join(f.temp, "secret"); await fs.writeFile(outside, "outside bytes"); + await fs.symlink(kind === "absolute" ? outside : "../secret", path.join(f.root, "unsafe")); + await fs.writeFile(path.join(f.root, "unchanged"), "edited"); + await fs.writeFile(path.join(f.root, "new"), "new output"); + await fs.unlink(path.join(f.root, "deleted")); + await fs.symlink("new", path.join(f.root, "safe")); + const result = await f.capture(); + expect(result.snapshot.entries.has("unsafe")).toBe(false); + await expect(fs.lstat(path.join(result.payload, "unsafe"))).rejects.toMatchObject({ code: "ENOENT" }); + await mergeDirectoryWithBaseline({ baseline: f.baseline, sourceDir: result.payload, targetDir: host, snapshots: { source: result.snapshot } }); + expect(await fs.readFile(path.join(host, "unchanged"), "utf8")).toBe("edited"); + expect(await fs.readFile(path.join(host, "new"), "utf8")).toBe("new output"); + expect(await fs.readlink(path.join(host, "safe"))).toBe("new"); + await expect(fs.lstat(path.join(host, "unsafe"))).rejects.toMatchObject({ code: "ENOENT" }); + await expect(fs.lstat(path.join(host, "deleted"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await fs.readFile(outside, "utf8")).toBe("outside bytes"); + await disposeDirectorySnapshot(result.snapshot); await disposeDirectorySnapshot(f.baseline); + }); + it("rejects an unsafe link forged into a checkpoint manifest before merge", async () => { + const f = await fixture(); const result = await f.capture(); + const entries = new Map(result.snapshot.entries); + entries.set("unsafe", { kind: "symlink", target: "../secret" }); + await expect(validateCheckpointPayload({ baseline: f.baseline, snapshot: { ...result.snapshot, entries }, payload: result.payload })) + .rejects.toMatchObject({ code: "WORKSPACE_RESTORE_UNSAFE_ARCHIVE" }); + await disposeDirectorySnapshot(result.snapshot); await disposeDirectorySnapshot(f.baseline); + }); + it("replays a checkpoint after interruption and preserves a concurrently edited deletion", async () => { + const f = await fixture(); const host = path.join(f.temp, "host"); await fs.cp(f.root, host, { recursive: true }); + await fs.writeFile(path.join(f.root, "new"), "remote"); await fs.unlink(path.join(f.root, "deleted")); + await fs.writeFile(path.join(host, "deleted"), "host edit"); + const result = await f.capture(); + await mergeDirectoryWithBaseline({ baseline: f.baseline, sourceDir: result.payload, targetDir: host, snapshots: { source: result.snapshot } }); + const replay = readCheckpointSnapshot(path.join(f.temp, "output", "manifest.sqlite"), f.baseline); + await validateCheckpointPayload({ baseline: f.baseline, snapshot: replay, payload: result.payload }); + await mergeDirectoryWithBaseline({ baseline: f.baseline, sourceDir: result.payload, targetDir: host, snapshots: { source: replay } }); + expect(await fs.readFile(path.join(host, "deleted"), "utf8")).toBe("host edit"); + expect(await fs.readFile(path.join(host, "new"), "utf8")).toBe("remote"); + await disposeDirectorySnapshot(f.baseline); + }); + it("rejects bytes changed after capture before applying the manifest", async () => { + const f = await fixture(); await fs.writeFile(path.join(f.root, "new"), "remote"); + const result = await f.capture(); await fs.writeFile(path.join(result.payload, "new"), "tampered"); + await expect(validateCheckpointPayload({ baseline: f.baseline, snapshot: result.snapshot, payload: result.payload })).rejects.toThrow("payload mismatch"); + await disposeDirectorySnapshot(result.snapshot); await disposeDirectorySnapshot(f.baseline); + }); + it.each(["array", "manifest"] as const)("rejects ignored entries and ancestor replacements before merge with %s ignore paths", async storage => { + for (const attack of ["matching_file", "matching_descendant", "payload_only", "ancestor_file", "ancestor_link"] as const) { + const f = await fixture(); await disposeDirectorySnapshot(f.baseline); + await fs.writeFile(path.join(f.root, ".env"), "host secret"); + await fs.mkdir(path.join(f.root, "private")); await fs.writeFile(path.join(f.root, "private", "token"), "private token"); + await fs.mkdir(path.join(f.root, "config")); await fs.writeFile(path.join(f.root, "config", ".env"), "nested secret"); + const paths = [".env", "private", "config/.env"]; + const writer = new WorkspaceManifestWriter(path.join(f.temp, "ignored.sqlite")); + for (const relative of paths) writer.add("ignored", relative); + const ignoredPaths = storage === "manifest" ? writer.paths("ignored") : paths; + writer.close(); + const baseline = await captureDirectorySnapshot(f.root, { exclude: ["cache"], ignoredPaths, diskBacked: true }); + const payload = path.join(f.temp, "forged-payload"); await fs.mkdir(payload); + await fs.writeFile(path.join(payload, "unchanged"), "otherwise valid edit"); + if (attack === "matching_file" || attack === "payload_only") await fs.writeFile(path.join(payload, ".env"), "injected secret"); + if (attack === "matching_descendant") { + await fs.mkdir(path.join(payload, "private")); await fs.writeFile(path.join(payload, "private", "token"), "injected token"); + } + if (attack === "ancestor_file") await fs.writeFile(path.join(payload, "config"), "replace directory"); + if (attack === "ancestor_link") await fs.symlink("unchanged", path.join(payload, "config")); + const captured = await captureDirectorySnapshot(payload); + const entries = new Map(baseline.entries); + entries.delete("deleted"); + for (const [relative, entry] of captured.entries) if (!(attack === "payload_only" && relative === ".env")) entries.set(relative, entry); + if (attack === "matching_descendant") entries.delete("private"); + const manifestPath = path.join(f.temp, "forged.sqlite"); + await writeCheckpointBaseline({ ...baseline, entries }, manifestPath); + const snapshot = readCheckpointSnapshot(manifestPath, baseline); + try { + await expect((async () => { + await validateCheckpointPayload({ baseline, snapshot, payload }); + await mergeDirectoryWithBaseline({ baseline, sourceDir: payload, targetDir: f.root, snapshots: { source: snapshot } }); + })()).rejects.toThrow(attack.startsWith("ancestor") ? "ignored path ancestor" : "Excluded workspace checkpoint entry"); + expect(await fs.readFile(path.join(f.root, "unchanged"), "utf8")).toBe("retained"); + expect(await fs.readFile(path.join(f.root, "deleted"), "utf8")).toBe("delete me"); + expect(await fs.readFile(path.join(f.root, ".env"), "utf8")).toBe("host secret"); + expect(await fs.readFile(path.join(f.root, "private", "token"), "utf8")).toBe("private token"); + expect(await fs.readFile(path.join(f.root, "config", ".env"), "utf8")).toBe("nested secret"); + } finally { await disposeDirectorySnapshot(snapshot); await disposeDirectorySnapshot(baseline); } + } + }); + it("allows directory tombstones while retaining ignored host children", async () => { + const f = await fixture(); await disposeDirectorySnapshot(f.baseline); + await fs.mkdir(path.join(f.root, "config")); + await fs.writeFile(path.join(f.root, "config", ".env"), "host only"); + await fs.writeFile(path.join(f.root, "config", "tracked"), "delete tracked child"); + const baseline = await captureDirectorySnapshot(f.root, { exclude: ["cache"], ignoredPaths: ["config/.env"], diskBacked: true }); + const payload = path.join(f.temp, "payload"); await fs.mkdir(payload); + await fs.writeFile(path.join(payload, "unchanged"), "valid edit"); + const captured = await captureDirectorySnapshot(payload); + const snapshot = { ...baseline, entries: new Map(captured.entries) }; + await validateCheckpointPayload({ baseline, snapshot, payload }); + await mergeDirectoryWithBaseline({ baseline, sourceDir: payload, targetDir: f.root, snapshots: { source: snapshot } }); + expect(await fs.readFile(path.join(f.root, "config", ".env"), "utf8")).toBe("host only"); + expect(await fs.readFile(path.join(f.root, "unchanged"), "utf8")).toBe("valid edit"); + await expect(fs.stat(path.join(f.root, "config", "tracked"))).rejects.toMatchObject({ code: "ENOENT" }); + await expect(fs.stat(path.join(f.root, "deleted"))).rejects.toMatchObject({ code: "ENOENT" }); + await disposeDirectorySnapshot(baseline); + }); + it("reuses durable seed generations and rejects corruption after run cleanup", async () => { + const f = await fixture(); const cache = path.join(f.temp, "cache-seeds"); + const archive = path.join(f.temp, "seed.tar"); await fs.writeFile(archive, "archive"); + const generation = workspaceSeedGeneration(f.baseline, null); + await publishWorkspaceSeedGeneration(cache, generation, { workspaceArchivePath: archive }); + await fs.unlink(archive); + const cached = await readWorkspaceSeedGeneration(cache, generation); expect(cached).not.toBeNull(); + expect(await fs.readFile(cached!.workspaceArchivePath, "utf8")).toBe("archive"); + await fs.writeFile(cached!.workspaceArchivePath, "corrupted"); + expect(await readWorkspaceSeedGeneration(cache, generation)).toBeNull(); + await disposeDirectorySnapshot(f.baseline); + }); +}); + +it("uses sparse native transfer end-to-end, including replay and unsupported-runtime fallback", async () => { + const { prepareSandboxManagedRuntime } = await import("./sandbox-managed-runtime.js"); + const f = await fixture(); await disposeDirectorySnapshot(f.baseline); + const remote = path.join(f.temp, "remote"); + const cache = path.join(f.temp, "seeds"); + let supportsCheckpoint = true; + let payloadBytes = -1; + let mutateDuringPack = false; + let failVerification = false, verificationCalls = 0; + const run = async (command: string) => { + if (!supportsCheckpoint && command.startsWith("node --input-type=module")) throw new Error("node unavailable"); + await execute("sh", ["-c", command]); + }; + const sync = async (operations: import("./sandbox-managed-runtime.js").SandboxSyncOperation[]) => { + for (const operation of operations) { + for (const file of operation.files) { + await fs.mkdir(path.dirname(file.targetPath), { recursive: true }); + await fs.cp(file.sourcePath, file.targetPath, { recursive: true, verbatimSymlinks: true, + filter: (source) => !file.exclude?.some((exclude) => path.relative(file.sourcePath, source) === exclude) }); + if (file.sourcePath.endsWith("/output")) { + payloadBytes = JSON.parse(await fs.readFile(path.join(file.targetPath, "receipt.json"), "utf8")).bytes; + } + } + for (const command of operation.postUploadCommands ?? []) await run(command.command); + } + return { operations: operations.map((op) => ({ operationId: op.operationId, filesTransferred: 0, bytesTransferred: 0 })) }; + }; + const client = { makeDir: (dir: string) => fs.mkdir(dir, { recursive: true }).then(() => {}), + writeFile: (file: string, data: ArrayBuffer) => fs.writeFile(file, Buffer.from(data)), readFile: (file: string) => fs.readFile(file), + listFiles: (dir: string) => fs.readdir(dir), remove: (file: string) => fs.rm(file, { force: true, recursive: true }), run, + syncIn: sync, syncOut: sync }; + const prepare = (overrides: Partial[0]> = {}) => prepareSandboxManagedRuntime({ spec: { provider: "test", sandboxId: "test", remoteCwd: remote, timeoutMs: 30000, apiKey: null, transport: "sandbox" }, + adapterKey: "test", client, workspaceLocalDir: f.root, workspaceCheckpoint: true, + runtimeSpan: async (name, work) => { + if (mutateDuringPack && name === "pack") await fs.writeFile(path.join(f.root, "unchanged"), "concurrent change"); + if (name === "seed.verify") { + verificationCalls++; + const result = await work(); + if (failVerification) throw Object.assign(new Error("verification disk full"), { code: "ENOSPC" }); + return result; + } + return work(); + }, + workspaceSeedCacheDirectory: cache, workspaceDurableSeed: { workspaceArchivePath: path.join(f.temp, "run-seed.tar") }, ...overrides }); + const first = await prepare(); await first.restoreWorkspace(); expect(payloadBytes).toBe(0); + const generation = (await fs.readdir(cache))[0]!; + await fs.writeFile(path.join(cache, generation, "workspace.tar"), "corrupt cache"); + const second = await prepare(); + await fs.writeFile(path.join(remote, "new"), "changed"); await fs.unlink(path.join(remote, "deleted")); + const outside = path.join(f.temp, "outside"); await fs.writeFile(outside, "private"); + await fs.symlink(outside, path.join(remote, "absolute-link")); + await fs.symlink("../outside", path.join(remote, "escaping-link")); + await fs.symlink("new", path.join(remote, "safe-link")); + await second.restoreWorkspace(); expect(payloadBytes).toBe(7); + expect(await fs.readFile(path.join(f.root, "new"), "utf8")).toBe("changed"); + expect(await fs.readlink(path.join(f.root, "safe-link"))).toBe("new"); + for (const name of ["absolute-link", "escaping-link"]) { + await expect(fs.lstat(path.join(f.root, name))).rejects.toMatchObject({ code: "ENOENT" }); + } + expect(await fs.readFile(outside, "utf8")).toBe("private"); + await expect(fs.stat(path.join(f.root, "deleted"))).rejects.toMatchObject({ code: "ENOENT" }); + supportsCheckpoint = false; + const third = await prepare(); await fs.writeFile(path.join(remote, "new"), "fallback"); + await third.restoreWorkspace(); expect(await fs.readFile(path.join(f.root, "new"), "utf8")).toBe("fallback"); + const existingGenerations = await fs.readdir(cache); + mutateDuringPack = true; + const raced = await prepare(); + expect(await fs.readdir(cache)).toEqual(existingGenerations); + expect((await fs.stat(path.join(f.temp, "run-seed.tar"))).size).toBeGreaterThan(0); + await raced.cleanupWorkspaceSnapshot(); + mutateDuringPack = false; failVerification = true; + await fs.writeFile(path.join(f.root, "new"), "verification recovery"); + const verificationFailed = await prepare(); + expect(await fs.readdir(cache)).toEqual(existingGenerations); + const failedSnapshot = verificationFailed.workspaceSyncSnapshot!; + await fs.writeFile(path.join(f.root, "new"), "later host bytes"); + await fs.rm(remote, { recursive: true, force: true }); + const verificationRecovery = await prepare({ workspaceInboundMode: "durable_seed", workspaceBaseline: failedSnapshot.baseline, + workspaceGitSnapshot: failedSnapshot.gitSnapshot, workspaceRepositories: failedSnapshot.repositories }); + expect(await fs.readFile(path.join(remote, "new"), "utf8")).toBe("verification recovery"); + await verificationRecovery.cleanupWorkspaceSnapshot(); + failVerification = false; + mutateDuringPack = false; + for (let count = (await fs.readdir(cache)).length; count < 16; count++) { + await fs.mkdir(path.join(cache, `.pending-retained-${count}`)); + } + const saturated = await fs.readdir(cache); + await fs.writeFile(path.join(f.root, "new"), "quota recovery"); + const verificationCallsBeforeSaturation = verificationCalls; + const uncached = await prepare(); + expect(verificationCalls).toBe(verificationCallsBeforeSaturation); + expect(await fs.readdir(cache)).toEqual(saturated); + const snapshot = uncached.workspaceSyncSnapshot!; + await fs.writeFile(path.join(f.root, "new"), "later host edit"); + await fs.rm(remote, { recursive: true, force: true }); + const recovered = await prepare({ workspaceInboundMode: "durable_seed", workspaceBaseline: snapshot.baseline, + workspaceGitSnapshot: snapshot.gitSnapshot, workspaceRepositories: snapshot.repositories }); + expect(await fs.readFile(path.join(remote, "new"), "utf8")).toBe("quota recovery"); + expect(await fs.readFile(path.join(f.root, "new"), "utf8")).toBe("later host edit"); + expect(await fs.readdir(cache)).toEqual(saturated); + await recovered.cleanupWorkspaceSnapshot(); +}, 30000); + +async function exportFailureFixture(failure: "low_disk" | "full_root" | "full_special" | "digest" | "path" | "symlink") { + const { prepareSandboxManagedRuntime } = await import("./sandbox-managed-runtime.js"); + const { DatabaseSync } = await import("node:sqlite"); + const f = await fixture(); + await disposeDirectorySnapshot(f.baseline); + const remote = path.join(f.temp, "remote"), outside = path.join(f.temp, "outside"); + await fs.writeFile(outside, "private"); + const metrics: unknown[] = []; + const counts = { captures: 0, fullExports: 0, capacityFailures: 0 }; + const run = async (command: string) => { + const capture = command.includes("checkpoint.mjs"); + if (capture) counts.captures++; + try { await execute("sh", ["-c", command]); } + catch (error) { + if (capture && ["low_disk", "full_root", "full_special"].includes(failure)) { + expect(String(error)).toContain("Workspace checkpoint storage is below its free-space reserve"); + counts.capacityFailures++; + await expect(fs.stat(path.join(f.root, "new"))).rejects.toMatchObject({ code: "ENOENT" }); + } + throw error; + } + }; + const sync = async (operations: import("./sandbox-managed-runtime.js").SandboxSyncOperation[]) => { + for (const operation of operations) { + for (const file of operation.files) { + if (file.sourcePath === remote) { + counts.fullExports++; + if (failure === "full_root") { + await fs.rm(file.targetPath, { recursive: true, force: true }); + await fs.symlink(remote, file.targetPath); + continue; + } + } + await fs.mkdir(path.dirname(file.targetPath), { recursive: true }); + await fs.cp(file.sourcePath, file.targetPath, { recursive: true, verbatimSymlinks: true, + filter: source => !file.exclude?.some(exclude => path.relative(file.sourcePath, source) === exclude) }); + if (file.sourcePath === remote && failure === "full_special") await execute("mkfifo", [path.join(file.targetPath, "unsafe-pipe")]); + if (["low_disk", "full_root", "full_special"].includes(failure) && file.targetPath.endsWith("/checkpoint.mjs")) { + const script = await fs.readFile(file.targetPath, "utf8"); + await fs.writeFile(file.targetPath, script.replace("const __name", "fs.statfs = async () => ({ bavail: 128n * 1024n, bsize: 1024n });\nconst __name")); + } + if (file.sourcePath.endsWith("/output")) { + if (failure === "digest") await fs.writeFile(path.join(file.targetPath, "payload", "new"), "forged"); + else if (failure === "path" || failure === "symlink") { + const manifest = new DatabaseSync(path.join(file.targetPath, "manifest.sqlite")); + try { + manifest.prepare("INSERT INTO records VALUES('baseline',?,?)").run( + failure === "path" ? "../escape" : "escape", + JSON.stringify({ kind: "symlink", target: outside }), + ); + } finally { manifest.close(); } + } + } + } + for (const command of operation.postUploadCommands ?? []) await run(command.command); + } + return { operations: operations.map(op => ({ operationId: op.operationId, filesTransferred: 0, bytesTransferred: 0 })) }; + }; + const client = { makeDir: (dir: string) => fs.mkdir(dir, { recursive: true }).then(() => {}), + writeFile: (file: string, data: ArrayBuffer) => fs.writeFile(file, Buffer.from(data)), readFile: (file: string) => fs.readFile(file), + listFiles: (dir: string) => fs.readdir(dir), remove: (file: string) => fs.rm(file, { force: true, recursive: true }), run, + syncIn: sync, syncOut: sync }; + const prepare = (overrides: Partial[0]> = {}) => prepareSandboxManagedRuntime({ + spec: { provider: "test", sandboxId: "test", remoteCwd: remote, timeoutMs: 30000, apiKey: null, transport: "sandbox" }, + adapterKey: "test", client, workspaceLocalDir: f.root, workspaceCheckpoint: true, + onWorkspaceCheckpoint: async metric => { metrics.push(metric); }, ...overrides, + }); + const prepared = await prepare(); + await fs.writeFile(path.join(remote, "new"), "tiny export"); + await fs.unlink(path.join(remote, "deleted")); + return { ...f, remote, outside, prepared, prepare, metrics, counts }; +} + +it.each([false, true])("falls back once to a full tiny export below the sparse disk reserve (recovery=%s)", async recovery => { + const f = await exportFailureFixture("low_disk"); + const snapshot = f.prepared.workspaceSyncSnapshot!; + const runtime = recovery ? await f.prepare({ workspaceInboundMode: "adopt_remote", workspaceBaseline: snapshot.baseline, + workspaceGitSnapshot: snapshot.gitSnapshot, workspaceRepositories: snapshot.repositories }) : f.prepared; + await fs.writeFile(path.join(f.root, "host-only"), "concurrent host edit"); + await fs.symlink(f.outside, path.join(f.remote, "unsafe-link")); + await fs.symlink("../outside", path.join(f.remote, "escaping-link")); + await fs.symlink("new", path.join(f.remote, "safe-link")); + await runtime.restoreWorkspace(); + expect(f.counts).toEqual({ captures: 1, fullExports: 1, capacityFailures: 1 }); + expect(f.metrics).toEqual([{ mode: "full", scannedEntries: null, changedFiles: null, payloadBytes: null }]); + expect(await fs.readFile(path.join(f.root, "new"), "utf8")).toBe("tiny export"); + expect(await fs.readFile(path.join(f.root, "host-only"), "utf8")).toBe("concurrent host edit"); + await expect(fs.stat(path.join(f.root, "deleted"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await fs.readFile(f.outside, "utf8")).toBe("private"); + for (const name of ["unsafe-link", "escaping-link"]) { + await expect(fs.lstat(path.join(f.root, name))).rejects.toMatchObject({ code: "ENOENT" }); + } + expect(await fs.readlink(path.join(f.root, "safe-link"))).toBe("new"); + await runtime.cleanupWorkspaceSnapshot(); + if (recovery) await f.prepared.cleanupWorkspaceSnapshot(); +}, 30000); + +it.each(["digest", "path", "symlink"] as const)("never falls back after returned sparse %s validation fails", async attack => { + const f = await exportFailureFixture(attack); + await expect(f.prepared.restoreWorkspace()).rejects.toThrow(); + expect(f.counts.fullExports).toBe(0); + expect(f.metrics).toEqual([]); + await expect(fs.stat(path.join(f.root, "new"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await fs.readFile(path.join(f.root, "deleted"), "utf8")).toBe("delete me"); + expect(await fs.readFile(f.outside, "utf8")).toBe("private"); + await f.prepared.cleanupWorkspaceSnapshot(); +}, 30000); + +it.each(["full_root", "full_special"] as const)("rejects unsafe %s data before host writes after capacity fallback", async attack => { + const f = await exportFailureFixture(attack); + await expect(f.prepared.restoreWorkspace()).rejects.toThrow("Unsafe workspace export directory"); + expect(f.counts).toEqual({ captures: 1, fullExports: 1, capacityFailures: 1 }); + await expect(fs.stat(path.join(f.root, "new"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await fs.readFile(path.join(f.root, "deleted"), "utf8")).toBe("delete me"); + expect(await fs.readFile(f.outside, "utf8")).toBe("private"); + await f.prepared.cleanupWorkspaceSnapshot(); +}, 30000); diff --git a/packages/adapter-utils/src/workspace-checkpoint.ts b/packages/adapter-utils/src/workspace-checkpoint.ts new file mode 100644 index 00000000000..aacac54e2a8 --- /dev/null +++ b/packages/adapter-utils/src/workspace-checkpoint.ts @@ -0,0 +1,163 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { DatabaseSync } from "node:sqlite"; +import { WorkspaceManifestWriter, WorkspaceManifestMap, workspacePaths, workspacePathMatcher } from "./workspace-manifest.js"; +import { shouldExcludePath, excludePatternMatches, isRelativePathOrDescendant } from "./exclude-patterns.js"; +import { openDirectorySnapshot, type DirectorySnapshot } from "./workspace-restore-merge.js"; + +export async function writeCheckpointBaseline(snapshot: DirectorySnapshot, filePath: string): Promise { + const writer = new WorkspaceManifestWriter(filePath); + try { + for (const [relative, entry] of snapshot.entries) writer.add("baseline", relative, JSON.stringify(entry)); + if (snapshot.ignoredPaths) for (const relative of workspacePaths(snapshot.ignoredPaths)) writer.add("ignored", relative); + writer.close(); + } catch (error) { writer.close(false); throw error; } +} + +async function checkpointMain( + fs: typeof import("node:fs/promises"), path: typeof import("node:path"), + DatabaseSync: typeof import("node:sqlite").DatabaseSync, + createHash: typeof import("node:crypto").createHash, + createReadStream: typeof import("node:fs").createReadStream, + shouldExcludePath: (relative: string, exclude: readonly string[]) => boolean, +) { + const [rootArg, baselinePath, outputDir, exclusions] = process.argv.slice(2); + const root = await fs.realpath(rootArg!); + const exclude: string[] = JSON.parse(exclusions!); + await fs.mkdir(outputDir!, { recursive: true }); + const payload = path.join(outputDir!, "payload"); + await fs.mkdir(payload); + const baseline = new DatabaseSync(baselinePath!, { readOnly: true, allowExtension: false }); + baseline.exec("PRAGMA trusted_schema=OFF; PRAGMA cache_size=-1024; PRAGMA temp_store=FILE; PRAGMA mmap_size=0;"); + const lookup = baseline.prepare("SELECT value FROM records WHERE category='baseline' AND path=?"); + const ignored = baseline.prepare("SELECT 1 FROM records WHERE category='ignored' AND path=?"); + const result = new DatabaseSync(path.join(outputDir!, "manifest.sqlite")); + const space = await fs.statfs(outputDir!, { bigint: true }); + const available = space.bavail * space.bsize - 256n * 1024n * 1024n; + if (available <= 0n) throw new Error("Workspace checkpoint storage is below its free-space reserve"); + result.exec(`PRAGMA max_page_count=${Math.min(Number(available / 4n / 4096n), 4_294_967_294)}`); + result.exec("PRAGMA journal_mode=DELETE; PRAGMA cache_size=-1024; PRAGMA temp_store=FILE; CREATE TABLE records(category TEXT,path TEXT,value TEXT NOT NULL, PRIMARY KEY(category,path)) WITHOUT ROWID; BEGIN;"); + const insert = result.prepare("INSERT INTO records VALUES('baseline',?,?)"); + let files = 0, bytes = 0, scanned = 0; + const fingerprint = (s: Awaited>) => [s.dev, s.ino, s.mode, s.size, s.mtimeMs, s.ctimeMs].join(":"); + const hash = async (file: string) => { + const digest = createHash("sha256"); + for await (const chunk of createReadStream(file)) digest.update(chunk); + return digest.digest("hex"); + }; + const walk = async (relative = "") => { + const directory = path.join(root, relative); + if (await fs.realpath(directory) !== directory) throw new Error("Workspace directory changed during checkpoint capture"); + for await (const child of await fs.opendir(directory)) { + const name = relative ? `${relative}/${child.name}` : child.name; + if (Buffer.byteLength(name) > 65536) throw new Error("Invalid workspace manifest path"); + let ignoredPath = false; + for (let p = name; p; p = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "") { + if (ignored.get(p)) { ignoredPath = true; break; } + } + if (ignoredPath || shouldExcludePath(name, exclude)) continue; + const source = path.join(root, name); + const before = await fs.lstat(source); + scanned++; + let entry: { kind: string; hash?: string; mode?: number; target?: string }; + if (before.isDirectory()) entry = { kind: "dir" }; + else if (before.isFile()) entry = { kind: "file", mode: before.mode, hash: await hash(source) }; + else if (before.isSymbolicLink()) { + const target = await fs.readlink(source); + const resolved = path.resolve(path.dirname(source), target); + if (path.isAbsolute(target) || (resolved !== root && !resolved.startsWith(`${root}/`))) continue; + entry = { kind: "symlink", target }; + } else continue; + if (fingerprint(before) !== fingerprint(await fs.lstat(source))) throw new Error("Workspace changed during checkpoint capture"); + const encoded = JSON.stringify(entry); + insert.run(name, encoded); + if (lookup.get(name)?.value !== encoded) { + const destination = path.join(payload, name); + await fs.mkdir(path.dirname(destination), { recursive: true }); + if (entry.kind === "dir") await fs.mkdir(destination, { recursive: true }); + else if (entry.kind === "symlink") await fs.symlink(entry.target!, destination); + else { + await fs.copyFile(source, destination); + await fs.chmod(destination, before.mode); + if (await hash(destination) !== entry.hash || fingerprint(before) !== fingerprint(await fs.lstat(source))) throw new Error("Workspace changed during checkpoint copy"); + files++; bytes += before.size; + } + } + if (before.isDirectory()) await walk(name); + } + }; + try { + await walk(); + result.exec("COMMIT"); + await fs.writeFile(path.join(outputDir!, "receipt.json"), JSON.stringify({ version: 1, files, bytes, scanned })); + } finally { baseline.close(); result.close(); } +} + +export function workspaceCheckpointScript(): string { + return `import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nconst __name = (fn) => fn;\n${isRelativePathOrDescendant.toString()}\nfunction pathContainsSegmentOrDescendant(relative, segment) { return relative === segment || relative.startsWith(segment + '/') || relative.endsWith('/' + segment) || relative.includes('/' + segment + '/'); }\n${excludePatternMatches.toString()}\n${shouldExcludePath.toString()}\nawait (${checkpointMain.toString()})(fs, path, DatabaseSync, createHash, createReadStream, shouldExcludePath);\n`; +} + +export function readCheckpointSnapshot(filePath: string, baseline: DirectorySnapshot): DirectorySnapshot { + const db = new DatabaseSync(filePath, { readOnly: true, allowExtension: false }); + let count: number; + try { + db.exec("PRAGMA trusted_schema=OFF; PRAGMA cache_size=-1024; PRAGMA temp_store=FILE; PRAGMA mmap_size=0;"); + count = Number(db.prepare("SELECT count(*) AS count FROM records WHERE category='baseline'").get()!.count); + } finally { db.close(); } + const snapshot = openDirectorySnapshot({ version: 2, exclude: baseline.exclude, ignoredPaths: baseline.ignoredPaths, + entries: { kind: "path_manifest", version: 1, filePath, category: "baseline", count } }); + try { for (const _entry of snapshot.entries) { } } + catch (error) { (snapshot.entries as WorkspaceManifestMap).close(); throw error; } + return snapshot; +} + +export async function validateCheckpointPayload(input: { baseline: DirectorySnapshot; snapshot: DirectorySnapshot; payload: string }): Promise { + const { captureDirectorySnapshot, disposeDirectorySnapshot } = await import("./workspace-restore-merge.js"); + const ignored = workspacePathMatcher(input.baseline.ignoredPaths); + let actual: DirectorySnapshot | null = null; + const equal = (left: unknown, right: unknown) => JSON.stringify(left) === JSON.stringify(right); + const assertIncluded = (relative: string) => { + if (shouldExcludePath(relative, input.baseline.exclude) || ignored.matches(relative)) { + throw new Error("Excluded workspace checkpoint entry"); + } + }; + let changedFiles = 0, payloadBytes = 0; + try { + actual = await captureDirectorySnapshot(input.payload, { diskBacked: true }); + for (const [relative, entry] of input.snapshot.entries) { + assertIncluded(relative); + const separator = relative.lastIndexOf("/"); + if (separator >= 0 && input.snapshot.entries.get(relative.slice(0, separator))?.kind !== "dir") { + throw new Error("Invalid workspace checkpoint parent"); + } + if (entry.kind === "symlink") { + const target = path.resolve(input.payload, path.dirname(relative), entry.target); + if (path.isAbsolute(entry.target) || (target !== path.resolve(input.payload) && !target.startsWith(`${path.resolve(input.payload)}/`))) { + throw Object.assign(new Error("Unsafe workspace checkpoint symlink"), { code: "WORKSPACE_RESTORE_UNSAFE_ARCHIVE" }); + } + } + if (!equal(entry, input.baseline.entries.get(relative)) && !equal(entry, actual.entries.get(relative))) throw new Error("Workspace checkpoint payload mismatch"); + } + for (const [relative, entry] of actual.entries) { + assertIncluded(relative); + if (!equal(entry, input.snapshot.entries.get(relative))) throw new Error("Unexpected workspace checkpoint payload"); + if (entry.kind === "file") { changedFiles++; payloadBytes += (await fs.stat(path.join(input.payload, relative))).size; } + } + if (input.baseline.ignoredPaths) for (const relative of workspacePaths(input.baseline.ignoredPaths)) { + for (let slash = relative.lastIndexOf("/"); slash >= 0; slash = relative.lastIndexOf("/", slash - 1)) { + const ancestor = relative.slice(0, slash); + for (const entry of [input.snapshot.entries.get(ancestor), actual.entries.get(ancestor)]) { + if (entry && entry.kind !== "dir") throw new Error("Workspace checkpoint replaces an ignored path ancestor"); + } + } + } + return { mode: "sparse", scannedEntries: input.snapshot.entries.size, changedFiles, payloadBytes }; + } finally { ignored.close(); await disposeDirectorySnapshot(actual); } +} + +export interface WorkspaceCheckpointMetrics { + mode: "sparse" | "full"; + scannedEntries: number | null; + changedFiles: number | null; + payloadBytes: number | null; +} diff --git a/packages/adapter-utils/src/workspace-restore-merge.test.ts b/packages/adapter-utils/src/workspace-restore-merge.test.ts index bfa57996a9f..57d22515d83 100644 --- a/packages/adapter-utils/src/workspace-restore-merge.test.ts +++ b/packages/adapter-utils/src/workspace-restore-merge.test.ts @@ -17,6 +17,7 @@ import { parseDirectorySnapshot, serializeDirectorySnapshot, withDirectoryMergeLock, + withDirectoryPublicationLock, WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE, } from "./workspace-restore-merge.js"; @@ -31,6 +32,42 @@ describe("workspace restore merge", () => { } }); + it("locks absent publications independently while sharing the published target's merge lock", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "paperclip-publication-lock-")); + cleanupDirs.push(root); + const env = { PAPERCLIP_HOME: path.join(root, "home"), PAPERCLIP_INSTANCE_ID: "publication" }; + const first = path.join(root, "first"), second = path.join(root, "second"); + await withDirectoryPublicationLock(first, async canonical => { + expect(await lstat(first).catch(() => null)).toBeNull(); + await mkdir(canonical); + await withDirectoryPublicationLock(second, async other => { + await mkdir(other); + expect(other).not.toBe(canonical); + }, env); + const merge = vi.fn(); + await expect(withDirectoryMergeLock(first, merge, env, undefined, 30)).rejects.toMatchObject({ + code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE, + }); + expect(merge).not.toHaveBeenCalled(); + }, env); + await withDirectoryMergeLock(first, async canonical => expect(canonical).toBe(await realpath(first)), env); + }); + + it.skipIf(process.platform === "win32")("canonicalizes publication parents and rejects unsafe existing leaves", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "paperclip-publication-alias-")); + cleanupDirs.push(root); + const env = { PAPERCLIP_HOME: path.join(root, "home"), PAPERCLIP_INSTANCE_ID: "publication" }; + const parent = path.join(root, "parent"), alias = path.join(root, "alias"); + await mkdir(parent); await symlink(parent, alias); + await withDirectoryPublicationLock(path.join(alias, "child"), async canonical => { + expect(canonical).toBe(path.join(await realpath(parent), "child")); + }, env); + await symlink(parent, path.join(parent, "child")); + await expect(withDirectoryPublicationLock(path.join(alias, "child"), async () => undefined, env)).rejects.toThrow("not a plain directory"); + await rm(path.join(parent, "child")); await writeFile(path.join(parent, "child"), "occupied"); + await expect(withDirectoryPublicationLock(path.join(alias, "child"), async () => undefined, env)).rejects.toThrow("not a plain directory"); + }); + it("round-trips a deterministic durable snapshot and rejects traversal", async () => { const rootDir = await mkdtemp( path.join(os.tmpdir(), "paperclip-snapshot-"), diff --git a/packages/adapter-utils/src/workspace-restore-merge.ts b/packages/adapter-utils/src/workspace-restore-merge.ts index 5865d8b1c5e..23bdcd7b9c2 100644 --- a/packages/adapter-utils/src/workspace-restore-merge.ts +++ b/packages/adapter-utils/src/workspace-restore-merge.ts @@ -504,6 +504,40 @@ export async function withDirectoryMergeLock( // Canonicalize before we hash or lock: a retargeted symlink must not let the // lock protect one directory while the caller mutates another. const canonicalTargetDir = await fs.realpath(targetDir); + return withCanonicalDirectoryMergeLock(canonicalTargetDir, fn, env, diagnosticOperation, waitMs); +} + +export async function withDirectoryPublicationLock( + targetDir: string, + fn: (canonicalTargetDir: string) => Promise, + env: NodeJS.ProcessEnv = process.env, +): Promise { + const absoluteTarget = path.resolve(targetDir); + const parent = await fs.realpath(path.dirname(absoluteTarget)); + const canonicalTargetDir = path.join(parent, path.basename(absoluteTarget)); + const assertSafeLeaf = async () => { + const leaf = await fs.lstat(canonicalTargetDir).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (leaf && (!leaf.isDirectory() || leaf.isSymbolicLink())) { + throw new Error(`Directory publication target at ${canonicalTargetDir} is not a plain directory.`); + } + }; + await assertSafeLeaf(); + return withCanonicalDirectoryMergeLock(canonicalTargetDir, async () => { + await assertSafeLeaf(); + return fn(canonicalTargetDir); + }, env); +} + +async function withCanonicalDirectoryMergeLock( + canonicalTargetDir: string, + fn: (canonicalTargetDir: string) => Promise, + env: NodeJS.ProcessEnv, + diagnosticOperation?: DirectoryMergeLockOperation, + waitMs: number = LOCK_WAIT_MS, +): Promise { const lockRoot = await resolveDirectoryMergeLockRoot(env); const lockKey = createHash("sha256").update(canonicalTargetDir).digest("hex"); const releaseLock = await acquireDirectoryMergeLock(path.join(lockRoot, `${lockKey}.lock`), diagnosticOperation, waitMs); @@ -662,7 +696,7 @@ export async function mergeDirectoryWithBaseline(input: { afterApply?: () => Promise; /** Caller holds the target's writer lock and validated an immutable sparse * source. Unchanged entries need no payload and are never copied. */ - snapshots?: { source: DirectorySnapshot; current: DirectorySnapshot }; + snapshots?: { source: DirectorySnapshot; current?: DirectorySnapshot }; }): Promise { const options = { exclude: input.baseline.exclude, ignoredPaths: input.baseline.ignoredPaths, diskBacked: true }; const source = input.snapshots?.source ?? await captureDirectorySnapshot(input.sourceDir, options); diff --git a/packages/adapter-utils/src/workspace-seed-cache.test.ts b/packages/adapter-utils/src/workspace-seed-cache.test.ts new file mode 100644 index 00000000000..956fbdedf5e --- /dev/null +++ b/packages/adapter-utils/src/workspace-seed-cache.test.ts @@ -0,0 +1,114 @@ +import fs from "node:fs/promises"; +import nodeFs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, expect, it, vi } from "vitest"; +import { publishWorkspaceSeedGeneration, readWorkspaceSeedGeneration } from "./workspace-seed-cache.js"; + +const roots: string[] = []; +afterEach(async () => { + vi.restoreAllMocks(); vi.unstubAllEnvs(); + await Promise.all(roots.splice(0).map(root => fs.rm(root, { recursive: true, force: true }))); +}); +async function fixture() { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "seed-cache-quota-")); roots.push(root); + vi.stubEnv("PAPERCLIP_HOME", path.join(root, "home")); + const company = path.join(root, "company"), workspace = path.join(company, "workspace"); + const archive = path.join(root, "run-seed.tar"); await fs.writeFile(archive, "run recovery bytes"); + const seed = { workspaceArchivePath: archive }; + return { root, company, workspace, archive, seed }; +} +const generation = (index: number) => index.toString(16).padStart(64, "0"); + +it("stops new workspace generations at capacity while retaining reusable and per-run seeds", async () => { + const f = await fixture(); const options = { companyDirectory: f.company, limits: { workspaceGenerations: 1 } }; + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, options)).toBe(true); + const prior = await readWorkspaceSeedGeneration(f.workspace, generation(1)); + expect(prior).not.toBeNull(); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(2), f.seed, options)).toBe(false); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, options)).toBe(true); + expect(await fs.readdir(f.workspace)).toEqual([generation(1)]); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); + expect(await fs.readFile(prior!.workspaceArchivePath, "utf8")).toBe("run recovery bytes"); +}); + +it.each(["workspaceBytes", "companyBytes"] as const)("counts archive and receipt bytes against %s without creating an empty workspace", async (limit) => { + const f = await fixture(); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, { + companyDirectory: f.company, limits: { [limit]: 4096 }, + })).toBe(false); + expect(await fs.readdir(f.company)).toEqual([]); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); +}); + +it.each(["companyGenerations", "companyWorkspaces"] as const)("serializes distinct-workspace publication against %s", async (limit) => { + const f = await fixture(); const options = { companyDirectory: f.company, limits: { [limit]: 1 } }; + const results = await Promise.all(["first", "second"].map((name, index) => + publishWorkspaceSeedGeneration(path.join(f.company, name), generation(index), f.seed, options))); + expect(results.sort()).toEqual([false, true]); + expect(await fs.readdir(f.company)).toHaveLength(1); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); +}); + +it("applies the company byte cap across workspace roots", async () => { + const f = await fixture(); + const options = { companyDirectory: f.company, limits: { companyBytes: 4500 } }; + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, options)).toBe(true); + const secondArchive = path.join(f.root, "second-seed.tar"); await fs.writeFile(secondArchive, "x".repeat(1000)); + expect(await publishWorkspaceSeedGeneration(path.join(f.company, "second"), generation(2), { workspaceArchivePath: secondArchive }, options)).toBe(false); + expect(await fs.readdir(f.company)).toEqual(["workspace"]); +}); + +it("serializes concurrent archive publication against the aggregate byte cap", async () => { + const f = await fixture(); await fs.writeFile(f.archive, "x".repeat(1024)); + const options = { companyDirectory: f.company, limits: { companyBytes: 5500 } }; + const results = await Promise.all(["first", "second"].map((name, index) => + publishWorkspaceSeedGeneration(path.join(f.company, name), generation(index), f.seed, options))); + expect(results.sort()).toEqual([false, true]); + expect(await fs.readdir(f.company)).toHaveLength(1); +}); + +it("counts abandoned pending and pre-existing over-quota directories without deleting them", async () => { + const f = await fixture(); await fs.mkdir(path.join(f.workspace, ".pending-old"), { recursive: true }); + await fs.writeFile(path.join(f.workspace, ".pending-old", "workspace.tar"), "retained"); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(2), f.seed, { + companyDirectory: f.company, limits: { workspaceGenerations: 0 }, + })).toBe(false); + expect(await fs.readFile(path.join(f.workspace, ".pending-old", "workspace.tar"), "utf8")).toBe("retained"); + expect(await fs.readdir(f.workspace)).toEqual([".pending-old"]); +}); + +it("reserves full-copy disk capacity and skips a cache write without losing recovery", async () => { + const f = await fixture(); + vi.spyOn(nodeFs, "statfsSync").mockReturnValue({ bavail: 256n * 1024n * 1024n, bsize: 1n } as ReturnType); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, { companyDirectory: f.company })).toBe(false); + expect(await fs.readdir(f.company)).toEqual([]); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); +}); + +it("supports cross-device archive copies independently of the per-run seed", async () => { + const f = await fixture(); + vi.spyOn(fs, "link").mockRejectedValue(Object.assign(new Error("cross-device"), { code: "EXDEV" })); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, { companyDirectory: f.company })).toBe(true); + await fs.unlink(f.archive); + const cached = await readWorkspaceSeedGeneration(f.workspace, generation(1)); + expect(await fs.readFile(cached!.workspaceArchivePath, "utf8")).toBe("run recovery bytes"); +}); + +it("skips optional verification when capacity is exhausted", async () => { + const f = await fixture(); const verify = vi.fn(async () => true); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, { + companyDirectory: f.company, limits: { workspaceGenerations: 0 }, verify, + })).toBe(false); + expect(verify).not.toHaveBeenCalled(); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); +}); + +it("treats optional verification storage failure as a cache miss", async () => { + const f = await fixture(); + expect(await publishWorkspaceSeedGeneration(f.workspace, generation(1), f.seed, { + companyDirectory: f.company, verify: async () => { throw Object.assign(new Error("disk full"), { code: "ENOSPC" }); }, + })).toBe(false); + expect(await fs.readdir(f.company)).toEqual([]); + expect(await fs.readFile(f.archive, "utf8")).toBe("run recovery bytes"); +}); diff --git a/packages/adapter-utils/src/workspace-seed-cache.ts b/packages/adapter-utils/src/workspace-seed-cache.ts new file mode 100644 index 00000000000..c35bcd4dfcc --- /dev/null +++ b/packages/adapter-utils/src/workspace-seed-cache.ts @@ -0,0 +1,134 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { createHash, randomUUID } from "node:crypto"; +import { createReadStream } from "node:fs"; +import { directorySnapshotSha256, withDirectoryMergeLock, type DirectorySnapshot } from "./workspace-restore-merge.js"; +import { assertWorkspaceManifestDiskSpace } from "./workspace-manifest.js"; +import type { GitWorkspaceSnapshot } from "./git-workspace-sync.js"; +import type { WorkspaceDurableSeedPaths } from "./sandbox-managed-runtime.js"; + +export function workspaceSeedGeneration(baseline: DirectorySnapshot, git: GitWorkspaceSnapshot | null, repositories: NonNullable = []): string { + const identity = (snapshot: GitWorkspaceSnapshot | null): unknown => snapshot && ({ + head: snapshot.headCommit, branch: snapshot.branchName, origin: snapshot.originUrl, + repositories: snapshot.repositories?.map((repo) => ({ path: repo.path, git: identity(repo.snapshot) })), + }); + return createHash("sha256").update("verified-seed-v1\0").update(JSON.stringify([directorySnapshotSha256(baseline), identity(git), repositories.map((repo) => ({ path: repo.path, git: identity(repo.snapshot) }))])).digest("hex"); +} +async function digest(file: string): Promise { + const hash = createHash("sha256"); + for await (const chunk of createReadStream(file)) hash.update(chunk); + return hash.digest("hex"); +} + +export async function readWorkspaceSeedGeneration(root: string, generation: string): Promise { + const directory = path.join(root, generation); + try { + for (const item of [root, directory]) { const stat = await fs.lstat(item); if (!stat.isDirectory() || stat.isSymbolicLink()) return null; } + const receipt = path.join(directory, "receipt.json"); + const receiptStat = await fs.lstat(receipt); + if (!receiptStat.isFile() || receiptStat.isSymbolicLink() || receiptStat.size > 4096) return null; + const metadata = JSON.parse(await fs.readFile(receipt, "utf8")); + if (!/^[a-f0-9]{64}$/.test(metadata.workspaceSha256) || (metadata.gitSha256 !== null && !/^[a-f0-9]{64}$/.test(metadata.gitSha256))) return null; + if (metadata.version !== 1 || metadata.generation !== generation) return null; + const workspaceArchivePath = path.join(directory, "workspace.tar"); + const gitArchivePath = metadata.gitSha256 ? path.join(directory, "git.tar") : null; + for (const [file, expected] of [[workspaceArchivePath, metadata.workspaceSha256], [gitArchivePath, metadata.gitSha256]]) { + if (!file) continue; + const stat = await fs.lstat(file); + if (!stat.isFile() || stat.isSymbolicLink() || await digest(file) !== expected) return null; + } + return { workspaceArchivePath, workspaceArchiveSha256: metadata.workspaceSha256, + gitArchivePath, gitArchiveSha256: metadata.gitSha256 }; + } catch { return null; } +} +export const WORKSPACE_SEED_CACHE_LIMITS = Object.freeze({ + workspaceGenerations: 16, + workspaceBytes: 1024 ** 3, + companyGenerations: 256, + companyWorkspaces: 256, + companyBytes: 8 * 1024 ** 3, +}); +type SeedCacheLimits = { [Key in keyof typeof WORKSPACE_SEED_CACHE_LIMITS]: number }; + +async function cacheUsage(root: string, limits: SeedCacheLimits): Promise<{ generations: number; bytes: number } | null> { + let generations = 0, bytes = 0; + for await (const entry of await fs.opendir(root)) { + if (++generations > limits.companyGenerations) return null; + const directory = path.join(root, entry.name); + const stat = await fs.lstat(directory); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + let files = 0; + for await (const child of await fs.opendir(directory)) { + if (++files > 3) return null; + const file = await fs.lstat(path.join(directory, child.name)); + if (!file.isFile() || file.isSymbolicLink()) return null; + bytes += file.size; + if (bytes > limits.companyBytes) return null; + } + } + return { generations, bytes }; +} + +export async function publishWorkspaceSeedGeneration( + root: string, generation: string, seed: WorkspaceDurableSeedPaths, + options: { companyDirectory?: string; limits?: Partial; verify?: () => Promise } = {}, +): Promise { + const limits = { ...WORKSPACE_SEED_CACHE_LIMITS, ...options.limits }; + if (!/^[a-f0-9]{64}$/.test(generation) + || Object.values(limits).some(value => !Number.isSafeInteger(value) || value < 0)) return false; + root = path.resolve(root); + const company = path.resolve(options.companyDirectory ?? root); + if (root !== company && path.dirname(root) !== company) return false; + try { + await fs.mkdir(company, { recursive: true, mode: 0o700 }); + const companyStat = await fs.lstat(company); + if (!companyStat.isDirectory() || companyStat.isSymbolicLink()) return false; + return await withDirectoryMergeLock(company, async () => { + let totalGenerations = 0, totalBytes = 0, workspaces = 0; + let workspace = { generations: 0, bytes: 0 }; + let workspaceExists = false; + const directories = root === company ? [{ name: "" }] : await fs.opendir(company); + for await (const { name } of directories) { + if (++workspaces > limits.companyWorkspaces) return false; + const directory = root === company ? company : path.join(company, name); + const stat = await fs.lstat(directory); + if (!stat.isDirectory() || stat.isSymbolicLink()) return false; + const usage = await cacheUsage(directory, limits); + if (!usage) return false; + totalGenerations += usage.generations; totalBytes += usage.bytes; + if (totalGenerations > limits.companyGenerations || totalBytes > limits.companyBytes) return false; + if (directory === root) { workspace = usage; workspaceExists = true; } + } + if (workspaceExists && await fs.lstat(path.join(root, generation)).then(() => true, () => false)) return true; + let incomingBytes = 4096; + for (const archive of [seed.workspaceArchivePath, seed.gitArchivePath]) { + if (!archive) continue; + const stat = await fs.lstat(archive); + if (!stat.isFile() || stat.isSymbolicLink()) return false; + incomingBytes += stat.size; + } + if (workspace.generations + 1 > limits.workspaceGenerations || workspace.bytes + incomingBytes > limits.workspaceBytes + || totalGenerations + 1 > limits.companyGenerations || totalBytes + incomingBytes > limits.companyBytes + || workspaces + (workspaceExists ? 0 : 1) > limits.companyWorkspaces) return false; + assertWorkspaceManifestDiskSpace(company, incomingBytes); + if (options.verify && !await options.verify()) return false; + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const temporary = path.join(root, `.pending-${randomUUID()}`); + await fs.mkdir(temporary, { mode: 0o700 }); + try { + const copy = async (source: string, name: string) => { + const target = path.join(temporary, name); + await fs.link(source, target).catch(() => fs.copyFile(source, target)); + const file = await fs.open(target, "r"); + try { await file.sync(); } finally { await file.close(); } + return digest(target); + }; + const workspaceSha256 = await copy(seed.workspaceArchivePath, "workspace.tar"); + const gitSha256 = seed.gitArchivePath ? await copy(seed.gitArchivePath, "git.tar") : null; + await fs.writeFile(path.join(temporary, "receipt.json"), JSON.stringify({ version: 1, generation, workspaceSha256, gitSha256 })); + await fs.rename(temporary, path.join(root, generation)); + return true; + } finally { await fs.rm(temporary, { recursive: true, force: true }); } + }); + } catch { return false; } +} diff --git a/packages/db/src/migrations/0336_big_crystal.sql b/packages/db/src/migrations/0336_big_crystal.sql new file mode 100644 index 00000000000..3f760467adf --- /dev/null +++ b/packages/db/src/migrations/0336_big_crystal.sql @@ -0,0 +1,48 @@ +CREATE TABLE IF NOT EXISTS "execution_workspace_repositories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "execution_workspace_id" uuid NOT NULL, + "requested_by_issue_id" uuid, + "repository_identity" text NOT NULL, + "catalog_repository_id" text, + "repo_url" text NOT NULL, + "relative_path" text NOT NULL, + "requested_ref" text DEFAULT 'HEAD' NOT NULL, + "pinned_commit" text, + "branch_name" text, + "request_key" text NOT NULL, + "request_keys" jsonb DEFAULT '[]'::jsonb NOT NULL, + "state" text DEFAULT 'pending' NOT NULL, + "failure_code" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "execution_workspaces" DROP CONSTRAINT IF EXISTS "execution_workspaces_project_id_projects_id_fk"; +--> statement-breakpoint +ALTER TABLE "execution_workspaces" ALTER COLUMN "project_id" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "chat_endpoint_resources" ADD COLUMN IF NOT EXISTS "execution_defaults" jsonb;--> statement-breakpoint +ALTER TABLE "chat_endpoints" ADD COLUMN IF NOT EXISTS "execution_defaults" jsonb;--> statement-breakpoint +ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "workspace_binding_revision" integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "workspace_selection" jsonb;--> statement-breakpoint +ALTER TABLE "issues" ADD COLUMN IF NOT EXISTS "workspace_pending_selection" jsonb;--> statement-breakpoint +DO $migration$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'execution_workspace_repositories'::regclass AND conname = 'execution_workspace_repositories_company_id_companies_id_fk'::name) THEN + ALTER TABLE "execution_workspace_repositories" ADD CONSTRAINT "execution_workspace_repositories_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $migration$;--> statement-breakpoint +DO $migration$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'execution_workspace_repositories'::regclass AND conname = 'execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk'::name) THEN + ALTER TABLE "execution_workspace_repositories" ADD CONSTRAINT "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $migration$;--> statement-breakpoint +DO $migration$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'execution_workspace_repositories'::regclass AND conname = 'execution_workspace_repositories_requested_by_issue_id_issues_id_fk'::name) THEN + ALTER TABLE "execution_workspace_repositories" ADD CONSTRAINT "execution_workspace_repositories_requested_by_issue_id_issues_id_fk" FOREIGN KEY ("requested_by_issue_id") REFERENCES "public"."issues"("id") ON DELETE set null ON UPDATE no action; + END IF; +END $migration$;--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "execution_workspace_repositories_identity_idx" ON "execution_workspace_repositories" USING btree ("execution_workspace_id","repository_identity");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "execution_workspace_repositories_path_idx" ON "execution_workspace_repositories" USING btree ("execution_workspace_id","relative_path");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "execution_workspace_repositories_request_idx" ON "execution_workspace_repositories" USING btree ("execution_workspace_id","request_key");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "execution_workspace_repositories_company_workspace_idx" ON "execution_workspace_repositories" USING btree ("company_id","execution_workspace_id");--> statement-breakpoint +ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action; diff --git a/packages/db/src/migrations/0337_exotic_proemial_gods.sql b/packages/db/src/migrations/0337_exotic_proemial_gods.sql new file mode 100644 index 00000000000..1fcc5b148de --- /dev/null +++ b/packages/db/src/migrations/0337_exotic_proemial_gods.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS "execution_workspace_repository_roots" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "canonical_cwd" text NOT NULL, + "root_key" text NOT NULL, + "mode" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $migration$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'execution_workspace_repository_roots'::regclass AND conname = 'execution_workspace_repository_roots_company_id_companies_id_fk'::name) THEN + ALTER TABLE "execution_workspace_repository_roots" ADD CONSTRAINT "execution_workspace_repository_roots_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $migration$;--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "execution_workspace_repository_roots_company_root_idx" ON "execution_workspace_repository_roots" USING btree ("company_id","root_key"); diff --git a/packages/db/src/migrations/0338_absurd_vengeance.sql b/packages/db/src/migrations/0338_absurd_vengeance.sql new file mode 100644 index 00000000000..e05c80bb305 --- /dev/null +++ b/packages/db/src/migrations/0338_absurd_vengeance.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS "execution_workspace_repository_access" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "root_key" text NOT NULL, + "canonical_cwd" text NOT NULL, + "kind" text NOT NULL, + "scope_id" uuid NOT NULL, + "issue_id" uuid, + "agent_id" uuid, + "responsible_user_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $migration$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'execution_workspace_repository_access'::regclass AND conname = 'execution_workspace_repository_access_company_id_companies_id_fk'::name) THEN + ALTER TABLE "execution_workspace_repository_access" ADD CONSTRAINT "execution_workspace_repository_access_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; + END IF; +END $migration$;--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "execution_workspace_repository_access_scope_idx" ON "execution_workspace_repository_access" USING btree ("company_id","root_key","kind","scope_id"); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0336_snapshot.json b/packages/db/src/migrations/meta/0336_snapshot.json new file mode 100644 index 00000000000..19604c5e2f5 --- /dev/null +++ b/packages/db/src/migrations/meta/0336_snapshot.json @@ -0,0 +1,59363 @@ +{ + "id": "a9f48522-347d-4290-83dd-07299afb8d03", + "prevId": "18c156f2-8344-4282-98b7-f702f0035c81", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounting_runtime_baselines": { + "name": "accounting_runtime_baselines", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounting_runtime_baselines_company_idx": { + "name": "accounting_runtime_baselines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounting_runtime_baselines_agent_id_agents_id_fk": { + "name": "accounting_runtime_baselines_agent_id_agents_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "accounting_runtime_baselines_company_id_companies_id_fk": { + "name": "accounting_runtime_baselines_company_id_companies_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.billing_invoice_lines": { + "name": "billing_invoice_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_id": { + "name": "invoice_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "billing_invoice_lines_identity_idx": { + "name": "billing_invoice_lines_identity_idx", + "columns": [ + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_invoice_lines_company_idx": { + "name": "billing_invoice_lines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoice_lines_company_id_companies_id_fk": { + "name": "billing_invoice_lines_company_id_companies_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_invoice_id_billing_invoices_id_fk": { + "name": "billing_invoice_lines_invoice_id_billing_invoices_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "billing_invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_cost_event_id_cost_events_id_fk": { + "name": "billing_invoice_lines_cost_event_id_cost_events_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_invoice_lines_amount_check": { + "name": "billing_invoice_lines_amount_check", + "value": "\"billing_invoice_lines\".\"amount_cents\" >= 0" + }, + "billing_invoice_lines_kind_check": { + "name": "billing_invoice_lines_kind_check", + "value": "\"billing_invoice_lines\".\"kind\" in ('inference', 'fee', 'credit')" + } + }, + "isRLSEnabled": false + }, + "public.billing_invoices": { + "name": "billing_invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imported_by": { + "name": "imported_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_invoices_identity_idx": { + "name": "billing_invoices_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoices_company_id_companies_id_fk": { + "name": "billing_invoices_company_id_companies_id_fk", + "tableFrom": "billing_invoices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_reservations": { + "name": "budget_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_invocation_id": { + "name": "decision_invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'held'" + }, + "provider_started_at": { + "name": "provider_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_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": { + "budget_reservations_fast_response_idx": { + "name": "budget_reservations_fast_response_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fast_response_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_decision_idx": { + "name": "budget_reservations_decision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_run_idx": { + "name": "budget_reservations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_active_idx": { + "name": "budget_reservations_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"budget_reservations\".\"state\" = 'held'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_reservations_company_id_companies_id_fk": { + "name": "budget_reservations_company_id_companies_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_run_id_heartbeat_runs_id_fk": { + "name": "budget_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_agent_id_agents_id_fk": { + "name": "budget_reservations_agent_id_agents_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_reservations_source_check": { + "name": "budget_reservations_source_check", + "value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int + (\"budget_reservations\".\"fast_response_request_id\" is not null)::int = 1" + }, + "budget_reservations_state_check": { + "name": "budget_reservations_state_check", + "value": "\"budget_reservations\".\"state\" in ('held', 'settled', 'released')" + }, + "budget_reservations_amount_check": { + "name": "budget_reservations_amount_check", + "value": "\"budget_reservations\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.cost_adjustments": { + "name": "cost_adjustments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_line_id": { + "name": "invoice_line_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_cents": { + "name": "previous_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "corrected_cents": { + "name": "corrected_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "previous_status": { + "name": "previous_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_pricing": { + "name": "previous_pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing": { + "name": "pricing", + "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": { + "cost_adjustments_receipt_idx": { + "name": "cost_adjustments_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_event_idx": { + "name": "cost_adjustments_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_invoice_line_idx": { + "name": "cost_adjustments_invoice_line_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_line_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_adjustments_company_id_companies_id_fk": { + "name": "cost_adjustments_company_id_companies_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_cost_event_id_cost_events_id_fk": { + "name": "cost_adjustments_cost_event_id_cost_events_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk": { + "name": "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "billing_invoice_lines", + "columnsFrom": [ + "invoice_line_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_adjustments_amount_check": { + "name": "cost_adjustments_amount_check", + "value": "\"cost_adjustments\".\"previous_cents\" >= 0 and \"cost_adjustments\".\"corrected_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.provider_billing_snapshots": { + "name": "provider_billing_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_billing_snapshots_identity_idx": { + "name": "provider_billing_snapshots_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_billing_snapshots_company_id_companies_id_fk": { + "name": "provider_billing_snapshots_company_id_companies_id_fk", + "tableFrom": "provider_billing_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "provider_billing_snapshots_amount_check": { + "name": "provider_billing_snapshots_amount_check", + "value": "\"provider_billing_snapshots\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.run_usage_receipts": { + "name": "run_usage_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_json": { + "name": "receipt_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_usage_receipts_source_sequence_idx": { + "name": "run_usage_receipts_source_sequence_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_usage_receipts_run_latest_idx": { + "name": "run_usage_receipts_run_latest_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_usage_receipts_company_id_companies_id_fk": { + "name": "run_usage_receipts_company_id_companies_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "run_usage_receipts_run_id_heartbeat_runs_id_fk": { + "name": "run_usage_receipts_run_id_heartbeat_runs_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "run_usage_receipts_positive_sequence": { + "name": "run_usage_receipts_positive_sequence", + "value": "\"run_usage_receipts\".\"sequence\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ai_connection": { + "name": "ai_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_grant_id": { + "name": "connection_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_method": { + "name": "connection_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_claim": { + "name": "result_claim", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commentary": { + "name": "agent_commentary", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_commentary_run_key_uq": { + "name": "agent_commentary_run_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commentary_company_kind_created_idx": { + "name": "agent_commentary_company_kind_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commentary_company_id_companies_id_fk": { + "name": "agent_commentary_company_id_companies_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_agent_id_agents_id_fk": { + "name": "agent_commentary_agent_id_agents_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_run_id_heartbeat_runs_id_fk": { + "name": "agent_commentary_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_issue_id_issues_id_fk": { + "name": "agent_commentary_issue_id_issues_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_commentary_kind_check": { + "name": "agent_commentary_kind_check", + "value": "\"agent_commentary\".\"kind\" in ('complaint', 'suggestion')" + } + }, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_identity_keys": { + "name": "agent_identity_keys", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key_pem": { + "name": "public_key_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_material": { + "name": "private_key_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_identity_keys_owner_fk": { + "name": "agent_identity_keys_owner_fk", + "tableFrom": "agent_identity_keys", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_identity_keys_algorithm_check": { + "name": "agent_identity_keys_algorithm_check", + "value": "\"agent_identity_keys\".\"algorithm\" = 'Ed25519'" + } + }, + "isRLSEnabled": false + }, + "public.agent_instruction_heads": { + "name": "agent_instruction_heads", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision_id": { + "name": "revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk": { + "name": "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk", + "tableFrom": "agent_instruction_heads", + "tableTo": "agent_instruction_revisions", + "columnsFrom": [ + "company_id", + "agent_id", + "entry_file", + "revision_id" + ], + "columnsTo": [ + "company_id", + "agent_id", + "entry_file", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_heads_identity_uq": { + "name": "agent_instruction_heads_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_revisions": { + "name": "agent_instruction_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_base64": { + "name": "content_base64", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_length": { + "name": "byte_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parent_revision_id": { + "name": "parent_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_revisions_history_idx": { + "name": "agent_instruction_revisions_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entry_file", + "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": { + "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_revisions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_revisions_identity_uq": { + "name": "agent_instruction_revisions_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_working_copies": { + "name": "agent_instruction_working_copies", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_hash": { + "name": "base_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_root": { + "name": "local_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_root": { + "name": "execution_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prepared'" + }, + "candidate_base64": { + "name": "candidate_base64", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "candidate_hash": { + "name": "candidate_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "process_stopped_at": { + "name": "process_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_copies_pending_idx": { + "name": "agent_instruction_copies_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_instruction_copies_agent_idx": { + "name": "agent_instruction_copies_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk": { + "name": "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_session_goal_actions": { + "name": "agent_session_goal_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_goal_actions_session_request_uniq": { + "name": "agent_session_goal_actions_session_request_uniq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_company_status_created_idx": { + "name": "agent_session_goal_actions_company_status_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_session_created_idx": { + "name": "agent_session_goal_actions_session_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_session_goal_actions_company_id_companies_id_fk": { + "name": "agent_session_goal_actions_company_id_companies_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_session_goal_actions_session_id_agent_task_sessions_id_fk": { + "name": "agent_session_goal_actions_session_id_agent_task_sessions_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "agent_task_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_capability_json": { + "name": "goal_capability_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_json": { + "name": "goal_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_status": { + "name": "goal_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_desired_state": { + "name": "goal_desired_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_id": { + "name": "goal_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_cursor": { + "name": "goal_source_cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "goal_revision": { + "name": "goal_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goal_observed_at": { + "name": "goal_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' OR \"agent_wakeup_requests\".\"idempotency_key\" LIKE 'interaction:%') AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_history_idx": { + "name": "agent_wakeup_requests_question_response_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'connection-intent:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_tool_action_delivery_uq": { + "name": "agent_wakeup_requests_tool_action_delivery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'tool-action-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_chat_completion_uq": { + "name": "agent_wakeup_requests_chat_completion_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'chat-completion:%'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appearance": { + "name": "appearance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "lifecycle_required_plugin_ids": { + "name": "lifecycle_required_plugin_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lifecycle_holds": { + "name": "lifecycle_holds", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "lifecycle_version": { + "name": "lifecycle_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lifecycle_error": { + "name": "lifecycle_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_operation": { + "name": "lifecycle_operation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_lifecycle_work_idx": { + "name": "agents_lifecycle_work_idx", + "columns": [ + { + "expression": "lifecycle_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_company_id_uq": { + "name": "agents_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_defaults": { + "name": "ai_connection_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_defaults_owner_method_uq": { + "name": "ai_connection_defaults_owner_method_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_defaults_company_id_companies_id_fk": { + "name": "ai_connection_defaults_company_id_companies_id_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_defaults_company_grant_fk": { + "name": "ai_connection_defaults_company_grant_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_connection_defaults_provider_check": { + "name": "ai_connection_defaults_provider_check", + "value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + }, + "ai_connection_defaults_method_check": { + "name": "ai_connection_defaults_method_check", + "value": "\"ai_connection_defaults\".\"method\" in ('subscription','api_key')" + } + }, + "isRLSEnabled": false + }, + "public.ai_connection_pools": { + "name": "ai_connection_pools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_pools_id_tool_connections_id_fk": { + "name": "ai_connection_pools_id_tool_connections_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_id_companies_id_fk": { + "name": "ai_connection_pools_company_id_companies_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_connection_fk": { + "name": "ai_connection_pools_company_connection_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_connection_pools_company_id_uq": { + "name": "ai_connection_pools_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_router_cursors": { + "name": "ai_connection_router_cursors", + "schema": "", + "columns": { + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "last_member_id": { + "name": "last_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_id_companies_id_fk": { + "name": "ai_connection_router_cursors_company_id_companies_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_pool_fk": { + "name": "ai_connection_router_cursors_company_pool_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_task_pins": { + "name": "ai_connection_task_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selection": { + "name": "selection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "member": { + "name": "member", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_task_pins_affinity_uq": { + "name": "ai_connection_task_pins_affinity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_task_pins_company_id_companies_id_fk": { + "name": "ai_connection_task_pins_company_id_companies_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_agent_id_agents_id_fk": { + "name": "ai_connection_task_pins_agent_id_agents_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_pool_fk": { + "name": "ai_connection_task_pins_company_pool_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_agent_fk": { + "name": "ai_connection_task_pins_company_agent_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_provider_defaults": { + "name": "ai_provider_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_provider_defaults_owner_provider_uq": { + "name": "ai_provider_defaults_owner_provider_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_provider_defaults_company_id_companies_id_fk": { + "name": "ai_provider_defaults_company_id_companies_id_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_provider_defaults_company_grant_fk": { + "name": "ai_provider_defaults_company_grant_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_provider_defaults_provider_check": { + "name": "ai_provider_defaults_provider_check", + "value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscription_connections": { + "name": "ai_subscription_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ai_subscription_connections_grant_idx": { + "name": "ai_subscription_connections_grant_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_credential_idx": { + "name": "ai_subscription_connections_credential_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_account_idx": { + "name": "ai_subscription_connections_account_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_connections_company_id_companies_id_fk": { + "name": "ai_subscription_connections_company_id_companies_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_connection_id_tool_connections_id_fk": { + "name": "ai_subscription_connections_connection_id_tool_connections_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_grant_id_connection_grants_id_fk": { + "name": "ai_subscription_connections_grant_id_connection_grants_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_subscription_prices": { + "name": "ai_subscription_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'month'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscription_prices_revision_idx": { + "name": "ai_subscription_prices_revision_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_prices_company_idx": { + "name": "ai_subscription_prices_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_prices_company_id_companies_id_fk": { + "name": "ai_subscription_prices_company_id_companies_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscription_prices_valid": { + "name": "ai_subscription_prices_valid", + "value": "(\"ai_subscription_prices\".\"amount_cents\" is null or \"ai_subscription_prices\".\"amount_cents\" >= 0) and \"ai_subscription_prices\".\"cadence\" in ('month','year') and \"ai_subscription_prices\".\"status\" in ('active','ended','excluded') and \"ai_subscription_prices\".\"source\" in ('catalog','user','unknown')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscriptions": { + "name": "ai_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_key": { + "name": "account_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity_verified": { + "name": "identity_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_ids": { + "name": "owner_user_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "detected_plan": { + "name": "detected_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_status": { + "name": "refresh_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_into_id": { + "name": "merged_into_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscriptions_identity_idx": { + "name": "ai_subscriptions_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscriptions_company_idx": { + "name": "ai_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscriptions_company_id_companies_id_fk": { + "name": "ai_subscriptions_company_id_companies_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk": { + "name": "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "merged_into_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscriptions_no_self_merge": { + "name": "ai_subscriptions_no_self_merge", + "value": "\"ai_subscriptions\".\"merged_into_id\" is null or \"ai_subscriptions\".\"merged_into_id\" <> \"ai_subscriptions\".\"id\"" + } + }, + "isRLSEnabled": false + }, + "public.announcement_dismissals": { + "name": "announcement_dismissals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "announcement_dismissals_user_id_announcement_id_pk": { + "name": "announcement_dismissals_user_id_announcement_id_pk", + "columns": [ + "user_id", + "announcement_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.announcement_publications": { + "name": "announcement_publications", + "schema": "", + "columns": { + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_browsers": { + "name": "browser_use_browsers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_browser_id": { + "name": "provider_browser_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "browser_use_browsers_provider_uq": { + "name": "browser_use_browsers_provider_uq", + "columns": [ + { + "expression": "provider_browser_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_browsers_session_idx": { + "name": "browser_use_browsers_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_browsers_company_id_companies_id_fk": { + "name": "browser_use_browsers_company_id_companies_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_browsers_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_browsers_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_runs": { + "name": "browser_use_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_run_id": { + "name": "provider_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "recovery_cursor": { + "name": "recovery_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'creating'" + }, + "detached_until": { + "name": "detached_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_cursor": { + "name": "event_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "events_drained": { + "name": "events_drained", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "accounted_cents": { + "name": "accounted_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress": { + "name": "progress", + "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()" + } + }, + "indexes": { + "browser_use_runs_invocation_uq": { + "name": "browser_use_runs_invocation_uq", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_provider_uq": { + "name": "browser_use_runs_provider_uq", + "columns": [ + { + "expression": "provider_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_session_idx": { + "name": "browser_use_runs_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_runs_company_id_companies_id_fk": { + "name": "browser_use_runs_company_id_companies_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_runs_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_sessions": { + "name": "browser_use_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "idle_deadline": { + "name": "idle_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_requested": { + "name": "stop_requested", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_poll_at": { + "name": "next_poll_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": { + "browser_use_sessions_task_idx": { + "name": "browser_use_sessions_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_sessions_provider_uq": { + "name": "browser_use_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_sessions_company_id_companies_id_fk": { + "name": "browser_use_sessions_company_id_companies_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_issue_id_issues_id_fk": { + "name": "browser_use_sessions_issue_id_issues_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_agent_id_agents_id_fk": { + "name": "browser_use_sessions_agent_id_agents_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_connection_id_tool_connections_id_fk": { + "name": "browser_use_sessions_connection_id_tool_connections_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_grant_id_connection_grants_id_fk": { + "name": "browser_use_sessions_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_settings": { + "name": "browser_use_settings", + "schema": "", + "columns": { + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allowed_profile_ids": { + "name": "allowed_profile_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "max_cost_usd": { + "name": "max_cost_usd", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "browser_use_settings_grant_id_connection_grants_id_fk": { + "name": "browser_use_settings_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_settings_company_id_companies_id_fk": { + "name": "browser_use_settings_company_id_companies_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" = 'open'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reservation_cents": { + "name": "reservation_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "unpriced_usage_policy": { + "name": "unpriced_usage_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'block'" + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enforcement_version": { + "name": "enforcement_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enforcement_delivered_version": { + "name": "enforcement_delivered_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_policies_reservation_nonnegative": { + "name": "budget_policies_reservation_nonnegative", + "value": "\"budget_policies\".\"reservation_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.chat_actions": { + "name": "chat_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_action_id": { + "name": "provider_action_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_actions_provider_action_uq": { + "name": "chat_actions_provider_action_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_actions_company_id_companies_id_fk": { + "name": "chat_actions_company_id_companies_id_fk", + "tableFrom": "chat_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_actions_delivery_id_chat_deliveries_id_fk": { + "name": "chat_actions_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_actions_company_delivery_fk": { + "name": "chat_actions_company_delivery_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_conversation_fk": { + "name": "chat_actions_company_conversation_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_principal_fk": { + "name": "chat_actions_company_principal_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_endpoint_fk": { + "name": "chat_actions_company_endpoint_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_agent_routes": { + "name": "chat_agent_routes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_endpoint_id": { + "name": "source_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "destination_endpoint_id": { + "name": "destination_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'explicit_mention'" + }, + "max_hops": { + "name": "max_hops", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_agent_routes_pair_uq": { + "name": "chat_agent_routes_pair_uq", + "columns": [ + { + "expression": "source_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "destination_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_agent_routes_company_id_companies_id_fk": { + "name": "chat_agent_routes_company_id_companies_id_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_source_fk": { + "name": "chat_agent_routes_company_source_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "source_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_destination_fk": { + "name": "chat_agent_routes_company_destination_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "destination_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_agent_routes_hops_check": { + "name": "chat_agent_routes_hops_check", + "value": "\"chat_agent_routes\".\"max_hops\" between 1 and 8" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_conversation_id": { + "name": "external_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_thread_id": { + "name": "external_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "external_label": { + "name": "external_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_direct_message": { + "name": "is_direct_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "communication_guidance": { + "name": "communication_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_conversations_issue_idx": { + "name": "chat_conversations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_conversations_thread_uq": { + "name": "chat_conversations_thread_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversations_company_id_companies_id_fk": { + "name": "chat_conversations_company_id_companies_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_resource_id_chat_endpoint_resources_id_fk": { + "name": "chat_conversations_resource_id_chat_endpoint_resources_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_conversations_issue_id_issues_id_fk": { + "name": "chat_conversations_issue_id_issues_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_conversations_company_issue_fk": { + "name": "chat_conversations_company_issue_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversations_company_endpoint_fk": { + "name": "chat_conversations_company_endpoint_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_company_resource_fk": { + "name": "chat_conversations_company_resource_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "company_id", + "resource_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversations_company_id_uq": { + "name": "chat_conversations_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_conversations_state_check": { + "name": "chat_conversations_state_check", + "value": "\"chat_conversations\".\"state\" in ('active', 'waiting', 'completed', 'unavailable', 'endpoint_removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_deliveries": { + "name": "chat_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_event": { + "name": "normalized_event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_deliveries_work_idx": { + "name": "chat_deliveries_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_event_uq": { + "name": "chat_deliveries_event_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_dedupe_uq": { + "name": "chat_deliveries_dedupe_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_deliveries_company_id_companies_id_fk": { + "name": "chat_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_conversation_id_chat_conversations_id_fk": { + "name": "chat_deliveries_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_principal_id_chat_external_principals_id_fk": { + "name": "chat_deliveries_principal_id_chat_external_principals_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "principal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_company_endpoint_fk": { + "name": "chat_deliveries_company_endpoint_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_company_conversation_fk": { + "name": "chat_deliveries_company_conversation_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_deliveries_company_principal_fk": { + "name": "chat_deliveries_company_principal_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_deliveries_company_id_uq": { + "name": "chat_deliveries_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_deliveries_state_check": { + "name": "chat_deliveries_state_check", + "value": "\"chat_deliveries\".\"state\" in ('received', 'filtered', 'processing', 'processed', 'retry', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoint_leases": { + "name": "chat_endpoint_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_leases_active_uq": { + "name": "chat_endpoint_leases_active_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_leases_expiry_idx": { + "name": "chat_endpoint_leases_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_leases_company_id_companies_id_fk": { + "name": "chat_endpoint_leases_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_leases_company_endpoint_fk": { + "name": "chat_endpoint_leases_company_endpoint_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_endpoint_resources": { + "name": "chat_endpoint_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_provider_resource_id": { + "name": "parent_provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "availability": { + "name": "availability", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_resources_endpoint_idx": { + "name": "chat_endpoint_resources_endpoint_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_resources_external_uq": { + "name": "chat_endpoint_resources_external_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_resources_company_id_companies_id_fk": { + "name": "chat_endpoint_resources_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_resources_company_endpoint_fk": { + "name": "chat_endpoint_resources_company_endpoint_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoint_resources_company_id_uq": { + "name": "chat_endpoint_resources_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoint_resources_availability_check": { + "name": "chat_endpoint_resources_availability_check", + "value": "\"chat_endpoint_resources\".\"availability\" in ('available', 'unavailable', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoints": { + "name": "chat_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publication_mode": { + "name": "publication_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'automatic'" + }, + "external_execution_policy": { + "name": "external_execution_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'restricted'" + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_user_id": { + "name": "sponsor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_account_label": { + "name": "provider_account_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_external_id": { + "name": "bot_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_username": { + "name": "bot_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_display_name": { + "name": "bot_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_avatar_url": { + "name": "bot_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communication_instructions": { + "name": "communication_instructions", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "require_at_mention": { + "name": "require_at_mention", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_group_chats": { + "name": "allow_group_chats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_unlinked_people": { + "name": "allow_unlinked_people", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"threads\":false,\"directMessages\":false,\"nativeStreaming\":false,\"messageEdits\":false,\"messageDeletes\":false,\"reactions\":false,\"files\":false,\"cards\":false,\"actions\":false,\"modals\":false,\"slashCommands\":false,\"ephemeralMessages\":false,\"proactiveDirectMessages\":false}'::jsonb" + }, + "setup": { + "name": "setup", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"step\":\"provider_setup\"}'::jsonb" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_publication_at": { + "name": "last_publication_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoints_company_idx": { + "name": "chat_endpoints_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agent_idx": { + "name": "chat_endpoints_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_status_idx": { + "name": "chat_endpoints_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_public_id_uq": { + "name": "chat_endpoints_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agentmail_inbox_uq": { + "name": "chat_endpoints_agentmail_inbox_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'agentmail' and \"chat_endpoints\".\"status\" != 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_connection_uq": { + "name": "chat_endpoints_connection_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_external_uq": { + "name": "chat_endpoints_live_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_discord_bot_external_uq": { + "name": "chat_endpoints_live_discord_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'discord'\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_global_app_bot_external_uq": { + "name": "chat_endpoints_live_global_app_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" in ('github', 'microsoft-teams')\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_username_uq": { + "name": "chat_endpoints_live_bot_username_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_username\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoints_company_id_companies_id_fk": { + "name": "chat_endpoints_company_id_companies_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoints_assigned_agent_id_agents_id_fk": { + "name": "chat_endpoints_assigned_agent_id_agents_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "assigned_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_endpoints_company_agent_fk": { + "name": "chat_endpoints_company_agent_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_endpoints_company_connection_fk": { + "name": "chat_endpoints_company_connection_fk", + "tableFrom": "chat_endpoints", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoints_company_id_uq": { + "name": "chat_endpoints_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoints_publication_mode_check": { + "name": "chat_endpoints_publication_mode_check", + "value": "\"chat_endpoints\".\"publication_mode\" in ('automatic', 'explicit')" + }, + "chat_endpoints_execution_policy_check": { + "name": "chat_endpoints_execution_policy_check", + "value": "\"chat_endpoints\".\"external_execution_policy\" in ('restricted', 'agent')" + }, + "chat_endpoints_email_policy_check": { + "name": "chat_endpoints_email_policy_check", + "value": "\"chat_endpoints\".\"provider\" <> 'agentmail' or (\"chat_endpoints\".\"publication_mode\" = 'explicit' and \"chat_endpoints\".\"external_execution_policy\" = 'agent')" + }, + "chat_endpoints_provider_check": { + "name": "chat_endpoints_provider_check", + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_endpoints_status_check": { + "name": "chat_endpoints_status_check", + "value": "\"chat_endpoints\".\"status\" in ('draft', 'verifying', 'active', 'paused', 'attention', 'revoked', 'archived')" + }, + "chat_endpoints_deployment_check": { + "name": "chat_endpoints_deployment_check", + "value": "\"chat_endpoints\".\"deployment_mode\" in ('direct', 'relay')" + }, + "chat_endpoints_concurrency_check": { + "name": "chat_endpoints_concurrency_check", + "value": "\"chat_endpoints\".\"concurrency_policy\" in ('burst', 'queue', 'debounce', 'drop', 'concurrent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_external_principals": { + "name": "chat_external_principals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_external_principals_company_idx": { + "name": "chat_external_principals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_external_principals_external_uq": { + "name": "chat_external_principals_external_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_external_principals_company_id_companies_id_fk": { + "name": "chat_external_principals_company_id_companies_id_fk", + "tableFrom": "chat_external_principals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_external_principals_company_id_uq": { + "name": "chat_external_principals_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_external_principals_provider_check": { + "name": "chat_external_principals_provider_check", + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_external_principals_kind_check": { + "name": "chat_external_principals_kind_check", + "value": "\"chat_external_principals\".\"kind\" in ('user', 'bot', 'app', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.chat_identity_links": { + "name": "chat_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paperclip_user_id": { + "name": "paperclip_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "confirmation_token_hash": { + "name": "confirmation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_identity_links_user_idx": { + "name": "chat_identity_links_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paperclip_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_identity_links_endpoint_principal_uq": { + "name": "chat_identity_links_endpoint_principal_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_identity_links_company_id_companies_id_fk": { + "name": "chat_identity_links_company_id_companies_id_fk", + "tableFrom": "chat_identity_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_endpoint_fk": { + "name": "chat_identity_links_company_endpoint_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_principal_fk": { + "name": "chat_identity_links_company_principal_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_identity_links_status_check": { + "name": "chat_identity_links_status_check", + "value": "\"chat_identity_links\".\"status\" in ('pending', 'linked', 'revoked', 'expired')" + } + }, + "isRLSEnabled": false + }, + "public.chat_message_links": { + "name": "chat_message_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_message_links_provider_message_uq": { + "name": "chat_message_links_provider_message_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_links_company_id_companies_id_fk": { + "name": "chat_message_links_company_id_companies_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_delivery_id_chat_deliveries_id_fk": { + "name": "chat_message_links_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_publication_id_chat_publications_id_fk": { + "name": "chat_message_links_publication_id_chat_publications_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_comment_id_issue_comments_id_fk": { + "name": "chat_message_links_comment_id_issue_comments_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_company_endpoint_fk": { + "name": "chat_message_links_company_endpoint_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_company_delivery_fk": { + "name": "chat_message_links_company_delivery_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_publication_fk": { + "name": "chat_message_links_company_publication_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_comment_fk": { + "name": "chat_message_links_company_comment_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_conversation_fk": { + "name": "chat_message_links_company_conversation_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_message_links_direction_check": { + "name": "chat_message_links_direction_check", + "value": "\"chat_message_links\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.chat_publications": { + "name": "chat_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_publications_company_id_uq": { + "name": "chat_publications_company_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_work_idx": { + "name": "chat_publications_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_idempotency_uq": { + "name": "chat_publications_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_publications_company_id_companies_id_fk": { + "name": "chat_publications_company_id_companies_id_fk", + "tableFrom": "chat_publications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_issue_id_issues_id_fk": { + "name": "chat_publications_issue_id_issues_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_publications_comment_id_issue_comments_id_fk": { + "name": "chat_publications_comment_id_issue_comments_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_publications_company_issue_fk": { + "name": "chat_publications_company_issue_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_comment_fk": { + "name": "chat_publications_company_comment_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_endpoint_fk": { + "name": "chat_publications_company_endpoint_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_company_conversation_fk": { + "name": "chat_publications_company_conversation_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_publications_state_check": { + "name": "chat_publications_state_check", + "value": "\"chat_publications\".\"state\" in ('pending', 'streaming', 'published', 'retry', 'delivery_unknown', 'failed', 'cancelled', 'awaiting_consent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_sdk_state": { + "name": "chat_sdk_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sdk_state_key_uq": { + "name": "chat_sdk_state_key_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sdk_state_expiry_idx": { + "name": "chat_sdk_state_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sdk_state_company_id_companies_id_fk": { + "name": "chat_sdk_state_company_id_companies_id_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sdk_state_company_endpoint_fk": { + "name": "chat_sdk_state_company_endpoint_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_completion_deliveries": { + "name": "chat_completion_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status_version": { + "name": "status_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response_comment_id": { + "name": "response_comment_id", + "type": "uuid", + "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": { + "chat_completion_deliveries_transition_uq": { + "name": "chat_completion_deliveries_transition_uq", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_completion_deliveries_pending_idx": { + "name": "chat_completion_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_completion_deliveries_company_id_companies_id_fk": { + "name": "chat_completion_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk": { + "name": "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "chat_task_handoffs", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "task_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_completion_deliveries_response_comment_id_issue_comments_id_fk": { + "name": "chat_completion_deliveries_response_comment_id_issue_comments_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "issue_comments", + "columnsFrom": [ + "response_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_task_handoffs": { + "name": "chat_task_handoffs", + "schema": "", + "columns": { + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_task_handoffs_source_idx": { + "name": "chat_task_handoffs_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_task_handoffs_task_id_issues_id_fk": { + "name": "chat_task_handoffs_task_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_company_id_companies_id_fk": { + "name": "chat_task_handoffs_company_id_companies_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_conversation_id_issues_id_fk": { + "name": "chat_task_handoffs_conversation_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_agent_id_agents_id_fk": { + "name": "chat_task_handoffs_agent_id_agents_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_discord_command_owners": { + "name": "chat_discord_command_owners", + "schema": "", + "columns": { + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_id": { + "name": "action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_discord_command_owners_application_check": { + "name": "chat_discord_command_owners_application_check", + "value": "\"chat_discord_command_owners\".\"application_id\" ~ '^[1-9][0-9]{16,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_configurations": { + "name": "chat_github_configurations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_configurations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_config_revision_check": { + "name": "chat_github_config_revision_check", + "value": "\"chat_github_configurations\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_registrations": { + "name": "chat_github_registrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trusted_origin": { + "name": "trusted_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "owner_login": { + "name": "owner_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handoff": { + "name": "handoff", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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": { + "chat_github_registration_state_uq": { + "name": "chat_github_registration_state_uq", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_registration_endpoint_idx": { + "name": "chat_github_registration_endpoint_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_registration_status_check": { + "name": "chat_github_registration_status_check", + "value": "\"chat_github_registrations\".\"status\" in ('pending', 'exchanging', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_reviews": { + "name": "chat_github_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pull_number": { + "name": "pull_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration_revision": { + "name": "configuration_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "policy_snapshot": { + "name": "policy_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "assessment": { + "name": "assessment", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "conclusion": { + "name": "conclusion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_url": { + "name": "check_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_id": { + "name": "summary_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_url": { + "name": "summary_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publication_receipts": { + "name": "publication_receipts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_github_reviews_delivery_uq": { + "name": "chat_github_reviews_delivery_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_pull_idx": { + "name": "chat_github_reviews_pull_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pull_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_task_idx": { + "name": "chat_github_reviews_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_reviews_run_id_heartbeat_runs_id_fk": { + "name": "chat_github_reviews_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk": { + "name": "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_github_reviews_company_id_uq": { + "name": "chat_github_reviews_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_github_review_pull_number_check": { + "name": "chat_github_review_pull_number_check", + "value": "\"chat_github_reviews\".\"pull_number\" > 0" + }, + "chat_github_review_state_check": { + "name": "chat_github_review_state_check", + "value": "\"chat_github_reviews\".\"state\" in ('queued', 'running', 'completed', 'incomplete', 'error', 'superseded', 'manual_required')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_manager_grants": { + "name": "chat_slack_manager_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_app_id": { + "name": "manager_app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "access_secret_id": { + "name": "access_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_secret_id": { + "name": "refresh_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rate_limits": { + "name": "rate_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_slack_manager_grants_company_id_idx": { + "name": "chat_slack_manager_grants_company_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_slack_manager_grants_owner_idx": { + "name": "chat_slack_manager_grants_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manager_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_slack_manager_grants_company_id_companies_id_fk": { + "name": "chat_slack_manager_grants_company_id_companies_id_fk", + "tableFrom": "chat_slack_manager_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_manager_grants_status_check": { + "name": "chat_slack_manager_grants_status_check", + "value": "\"chat_slack_manager_grants\".\"status\" in ('active', 'revoked', 'reauthorize')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_registrations": { + "name": "chat_slack_registrations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "manager_grant_id": { + "name": "manager_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "manifest_hash": { + "name": "manifest_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "callback_uri": { + "name": "callback_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_ids": { + "name": "secret_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_slack_manager_grants", + "columnsFrom": [ + "company_id", + "manager_grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_registration_status_check": { + "name": "chat_slack_registration_status_check", + "value": "\"chat_slack_registrations\".\"status\" in ('creating', 'uncertain', 'failed', 'install', 'credentials_saved', 'configured', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_teams_file_transfers": { + "name": "chat_teams_file_transfers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "authorized_user_id": { + "name": "authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_generation": { + "name": "runtime_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_generation": { + "name": "conversation_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_digest": { + "name": "source_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authority_digest": { + "name": "authority_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aad_object_id": { + "name": "aad_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_sha256": { + "name": "token_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'consent_pending'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_expires_at": { + "name": "attempt_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_message_id": { + "name": "consent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_info_message_id": { + "name": "file_info_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_activity_id": { + "name": "response_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_digest": { + "name": "response_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_state": { + "name": "private_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_teams_file_transfers_publication_uq": { + "name": "chat_teams_file_transfers_publication_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_token_uq": { + "name": "chat_teams_file_transfers_token_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_work_idx": { + "name": "chat_teams_file_transfers_work_idx", + "columns": [ + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_teams_file_transfers_company_id_companies_id_fk": { + "name": "chat_teams_file_transfers_company_id_companies_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_issue_id_issues_id_fk": { + "name": "chat_teams_file_transfers_issue_id_issues_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_teams_file_transfers_phase_check": { + "name": "chat_teams_file_transfers_phase_check", + "value": "\"chat_teams_file_transfers\".\"phase\" in ('consent_pending','consent_sending','consent_unknown','awaiting_consent','upload_pending','uploading','upload_unknown','file_info_pending','file_info_sending','file_info_unknown','delivered','declined','expired','cancelled','conflict')" + }, + "chat_teams_file_transfers_bounds_check": { + "name": "chat_teams_file_transfers_bounds_check", + "value": "\"chat_teams_file_transfers\".\"version\" > 0 and \"chat_teams_file_transfers\".\"runtime_generation\" >= 0 and \"chat_teams_file_transfers\".\"conversation_generation\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" < 62914560" + }, + "chat_teams_file_transfers_hash_check": { + "name": "chat_teams_file_transfers_hash_check", + "value": "\"chat_teams_file_transfers\".\"source_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"authority_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"sha256\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"token_sha256\" ~ '^[a-f0-9]{64}$'" + }, + "chat_teams_file_transfers_attempt_check": { + "name": "chat_teams_file_transfers_attempt_check", + "value": "(\"chat_teams_file_transfers\".\"attempt_id\" is null) = (\"chat_teams_file_transfers\".\"attempt_expires_at\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_callbacks": { + "name": "chat_voice_callbacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_callbacks_owner_uq": { + "name": "chat_voice_callbacks_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_callbacks_endpoint_fk": { + "name": "chat_voice_callbacks_endpoint_fk", + "tableFrom": "chat_voice_callbacks", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_callbacks_phone_check": { + "name": "chat_voice_callbacks_phone_check", + "value": "\"chat_voice_callbacks\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_inbound_calls": { + "name": "chat_voice_inbound_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "approval_code": { + "name": "approval_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "intake_issue_id": { + "name": "intake_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_issue_id": { + "name": "requested_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_inbound_expiry_idx": { + "name": "chat_voice_inbound_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_inbound_endpoint_fk": { + "name": "chat_voice_inbound_endpoint_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_inbound_session_fk": { + "name": "chat_voice_inbound_session_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_intake_fk": { + "name": "chat_voice_inbound_intake_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "intake_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_requested_issue_fk": { + "name": "chat_voice_inbound_requested_issue_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "requested_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_inbound_provider_uq": { + "name": "chat_voice_inbound_provider_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_inbound_state_check": { + "name": "chat_voice_inbound_state_check", + "value": "\"chat_voice_inbound_calls\".\"state\" in ('guest_intake', 'awaiting_approval', 'approving', 'approved', 'denied', 'expired', 'ended')" + }, + "chat_voice_inbound_token_check": { + "name": "chat_voice_inbound_token_check", + "value": "\"chat_voice_inbound_calls\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_inbound_code_check": { + "name": "chat_voice_inbound_code_check", + "value": "\"chat_voice_inbound_calls\".\"approval_code\" ~ '^[0-9]{6}$'" + }, + "chat_voice_inbound_generation_check": { + "name": "chat_voice_inbound_generation_check", + "value": "\"chat_voice_inbound_calls\".\"generation\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_phone_lines": { + "name": "chat_voice_phone_lines", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_number_id": { + "name": "provider_number_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest_intake": { + "name": "guest_intake", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "low_trust_environment_id": { + "name": "low_trust_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk": { + "name": "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "environments", + "columnsFrom": [ + "low_trust_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_phone_lines_endpoint_fk": { + "name": "chat_voice_phone_lines_endpoint_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_phone_lines_number_uq": { + "name": "chat_voice_phone_lines_number_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_number_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_phone_lines_number_check": { + "name": "chat_voice_phone_lines_number_check", + "value": "\"chat_voice_phone_lines\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_reports": { + "name": "chat_voice_reports", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "transcript": { + "name": "transcript", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cost_micro_usd": { + "name": "cost_micro_usd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_updated_at": { + "name": "provider_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_check_at": { + "name": "next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_reports_pending_idx": { + "name": "chat_voice_reports_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_reports_session_fk": { + "name": "chat_voice_reports_session_fk", + "tableFrom": "chat_voice_reports", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_reports_status_check": { + "name": "chat_voice_reports_status_check", + "value": "\"chat_voice_reports\".\"status\" in ('pending', 'available', 'unavailable')" + }, + "chat_voice_reports_cost_check": { + "name": "chat_voice_reports_cost_check", + "value": "\"chat_voice_reports\".\"cost_micro_usd\" is null or \"chat_voice_reports\".\"cost_micro_usd\" ~ '^[0-9]{1,30}$'" + }, + "chat_voice_reports_duration_check": { + "name": "chat_voice_reports_duration_check", + "value": "\"chat_voice_reports\".\"duration_seconds\" is null or \"chat_voice_reports\".\"duration_seconds\" >= 0" + }, + "chat_voice_reports_attempts_check": { + "name": "chat_voice_reports_attempts_check", + "value": "\"chat_voice_reports\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_replies": { + "name": "chat_voice_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "spoken_at": { + "name": "spoken_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": { + "chat_voice_replies_publication_uq": { + "name": "chat_voice_replies_publication_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_replies_cursor_uq": { + "name": "chat_voice_replies_cursor_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cursor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_replies_company_id_companies_id_fk": { + "name": "chat_voice_replies_company_id_companies_id_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_replies_session_fk": { + "name": "chat_voice_replies_session_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_replies_publication_fk": { + "name": "chat_voice_replies_publication_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_replies_cursor_check": { + "name": "chat_voice_replies_cursor_check", + "value": "\"chat_voice_replies\".\"cursor\" > 0" + }, + "chat_voice_replies_spoken_check": { + "name": "chat_voice_replies_spoken_check", + "value": "\"chat_voice_replies\".\"spoken_at\" is null or \"chat_voice_replies\".\"delivered_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_sessions": { + "name": "chat_voice_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_id": { + "name": "caller_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reply_cursor": { + "name": "reply_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_sessions_provider_uq": { + "name": "chat_voice_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_request_uq": { + "name": "chat_voice_sessions_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_live_caller_task_uq": { + "name": "chat_voice_sessions_live_caller_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_voice_sessions\".\"state\" not in ('ended', 'failed', 'expired')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_expiry_idx": { + "name": "chat_voice_sessions_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_sessions_company_id_companies_id_fk": { + "name": "chat_voice_sessions_company_id_companies_id_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_sessions_endpoint_fk": { + "name": "chat_voice_sessions_endpoint_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_conversation_fk": { + "name": "chat_voice_sessions_conversation_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_issue_fk": { + "name": "chat_voice_sessions_issue_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_agent_fk": { + "name": "chat_voice_sessions_agent_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_sessions_company_id_uq": { + "name": "chat_voice_sessions_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_sessions_state_check": { + "name": "chat_voice_sessions_state_check", + "value": "\"chat_voice_sessions\".\"state\" in ('reserved', 'creating', 'creation_unknown', 'connecting', 'active', 'awaiting_approval', 'ending', 'ended', 'failed', 'expired')" + }, + "chat_voice_sessions_mode_check": { + "name": "chat_voice_sessions_mode_check", + "value": "\"chat_voice_sessions\".\"mode\" in ('browser', 'inbound_phone', 'outbound_phone')" + }, + "chat_voice_sessions_authority_check": { + "name": "chat_voice_sessions_authority_check", + "value": "\"chat_voice_sessions\".\"caller_authority\" in ('member', 'instance_admin', 'local_board', 'guest_intake', 'pending_approval')" + }, + "chat_voice_sessions_counters_check": { + "name": "chat_voice_sessions_counters_check", + "value": "\"chat_voice_sessions\".\"generation\" >= 1 and \"chat_voice_sessions\".\"reply_cursor\" >= 0" + }, + "chat_voice_sessions_token_check": { + "name": "chat_voice_sessions_token_check", + "value": "\"chat_voice_sessions\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_sessions_expiry_check": { + "name": "chat_voice_sessions_expiry_check", + "value": "\"chat_voice_sessions\".\"expires_at\" > \"chat_voice_sessions\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_tool_calls": { + "name": "chat_voice_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_tool_call_id": { + "name": "provider_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_tool_calls_tool_uq": { + "name": "chat_voice_tool_calls_tool_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_tool_calls_webhook_uq": { + "name": "chat_voice_tool_calls_webhook_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_tool_calls_company_id_companies_id_fk": { + "name": "chat_voice_tool_calls_company_id_companies_id_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_session_fk": { + "name": "chat_voice_tool_calls_session_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_delivery_fk": { + "name": "chat_voice_tool_calls_delivery_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_tool_calls_tool_check": { + "name": "chat_voice_tool_calls_tool_check", + "value": "\"chat_voice_tool_calls\".\"tool\" in ('submit_request', 'get_updates', 'answer_question')" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ai_session_epoch": { + "name": "ai_session_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_source_entries": { + "name": "company_skill_source_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inspection": { + "name": "inspection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "selection": { + "name": "selection", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "present": { + "name": "present", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_skill_source_entries_source_path_idx": { + "name": "company_skill_source_entries_source_path_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_source_entries_company_skill_idx": { + "name": "company_skill_source_entries_company_skill_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_source_entries_company_id_companies_id_fk": { + "name": "company_skill_source_entries_company_id_companies_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_source_id_company_skill_sources_id_fk": { + "name": "company_skill_source_entries_source_id_company_skill_sources_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skill_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_skill_id_company_skills_id_fk": { + "name": "company_skill_source_entries_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_sources": { + "name": "company_skill_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_url": { + "name": "repository_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_ref": { + "name": "tracking_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "package_mode": { + "name": "package_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skills'" + }, + "repository_package": { + "name": "repository_package", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_folders": { + "name": "excluded_folders", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_scan_commit": { + "name": "last_scan_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_sources_repository_ref_idx": { + "name": "company_skill_sources_repository_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_sources_identity_ref_idx": { + "name": "company_skill_sources_identity_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_sources_company_id_companies_id_fk": { + "name": "company_skill_sources_company_id_companies_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_sources_connection_id_tool_connections_id_fk": { + "name": "company_skill_sources_connection_id_tool_connections_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_repository_snapshots": { + "name": "company_skill_repository_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_repository_snapshots_content_idx": { + "name": "company_skill_repository_snapshots_content_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_repository_snapshots_company_id_companies_id_fk": { + "name": "company_skill_repository_snapshots_company_id_companies_id_fk", + "tableFrom": "company_skill_repository_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_snapshot_id": { + "name": "repository_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_skill_path": { + "name": "repository_skill_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk": { + "name": "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skill_repository_snapshots", + "columnsFrom": [ + "repository_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_event_deliveries": { + "name": "connection_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_delivery_id": { + "name": "provider_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_payload": { + "name": "normalized_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_event_deliveries_company_provider_id_uq": { + "name": "connection_event_deliveries_company_provider_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_event_deliveries_company_status_idx": { + "name": "connection_event_deliveries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_event_deliveries_company_id_companies_id_fk": { + "name": "connection_event_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_event_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_intent_deliveries": { + "name": "connection_intent_deliveries", + "schema": "", + "columns": { + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_intent_deliveries_pending_idx": { + "name": "connection_intent_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_intent_deliveries_company_id_companies_id_fk": { + "name": "connection_intent_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_kind": { + "name": "usage_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_cost_cents": { + "name": "reported_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "pricing_provenance": { + "name": "pricing_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_receipt_idx": { + "name": "cost_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_provider_request_idx": { + "name": "cost_events_provider_request_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_project_occurred_idx": { + "name": "cost_events_company_project_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_unpriced_idx": { + "name": "cost_events_unpriced_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_data_quality_idx": { + "name": "cost_events_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"billing_type\" = 'unknown' or (\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_subscription_occurred_idx": { + "name": "cost_events_company_subscription_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_subscription_id_ai_subscriptions_id_fk": { + "name": "cost_events_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "cost_events", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_events_usage_kind_check": { + "name": "cost_events_usage_kind_check", + "value": "\"cost_events\".\"usage_kind\" in ('decision', 'fast_response') or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)" + }, + "cost_events_nonnegative_amounts": { + "name": "cost_events_nonnegative_amounts", + "value": "\"cost_events\".\"cost_cents\" >= 0 and \"cost_events\".\"input_tokens\" >= 0 and \"cost_events\".\"cached_input_tokens\" >= 0 and \"cost_events\".\"output_tokens\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.company_decision_models": { + "name": "company_decision_models", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_background": { + "name": "allow_background", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_decision_models_company_id_companies_id_fk": { + "name": "company_decision_models_company_id_companies_id_fk", + "tableFrom": "company_decision_models", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_decisions_configured": { + "name": "company_decisions_configured", + "value": "(\"company_decision_models\".\"connection_id\" is null) = (\"company_decision_models\".\"grant_id\" is null) and (not \"company_decision_models\".\"enabled\" or (\"company_decision_models\".\"connection_id\" is not null and \"company_decision_models\".\"provider\" is not null and \"company_decision_models\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.decision_invocations": { + "name": "decision_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "identity_context_id": { + "name": "identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "question_types": { + "name": "question_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_invocations_company_started_idx": { + "name": "decision_invocations_company_started_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "decision_invocations_pending_idx": { + "name": "decision_invocations_pending_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"decision_invocations\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_invocations_cost_idx": { + "name": "decision_invocations_cost_idx", + "columns": [ + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_invocations_company_id_companies_id_fk": { + "name": "decision_invocations_company_id_companies_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_invocations_cost_event_id_cost_events_id_fk": { + "name": "decision_invocations_cost_event_id_cost_events_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_invocations_status_check": { + "name": "decision_invocations_status_check", + "value": "\"decision_invocations\".\"status\" in ('running', 'succeeded', 'failed', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_agent_bindings": { + "name": "dot_agent_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operator_id": { + "name": "operator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pairing'" + }, + "pairing_code_hash": { + "name": "pairing_code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pairing_expires_at": { + "name": "pairing_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dot_url": { + "name": "dot_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_hash": { + "name": "challenge_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_expires_at": { + "name": "challenge_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ready_at": { + "name": "ready_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_bindings_active_agent_uq": { + "name": "dot_bindings_active_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_active_grant_uq": { + "name": "dot_bindings_active_grant_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL AND \"dot_agent_bindings\".\"grant_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_pairing_code_uq": { + "name": "dot_bindings_pairing_code_uq", + "columns": [ + { + "expression": "pairing_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_agent_bindings_company_id_companies_id_fk": { + "name": "dot_agent_bindings_company_id_companies_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_agent_id_agents_id_fk": { + "name": "dot_agent_bindings_agent_id_agents_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk": { + "name": "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_mailbox_items": { + "name": "dot_mailbox_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "references": { + "name": "references", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_mailbox_event_uq": { + "name": "dot_mailbox_event_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_mailbox_binding_cursor_idx": { + "name": "dot_mailbox_binding_cursor_idx", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_mailbox_items_company_id_companies_id_fk": { + "name": "dot_mailbox_items_company_id_companies_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_assignments": { + "name": "dot_runner_assignments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "normalized_session_id": { + "name": "normalized_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "catalog_digest": { + "name": "catalog_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projection": { + "name": "projection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "accept_by": { + "name": "accept_by", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_activity_at": { + "name": "last_activity_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()" + } + }, + "indexes": { + "dot_assignments_turn_uq": { + "name": "dot_assignments_turn_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_active_binding_uq": { + "name": "dot_assignments_active_binding_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_runner_assignments\".\"status\" IN ('offered', 'accepted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_company_idx": { + "name": "dot_assignments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_assignments_company_id_companies_id_fk": { + "name": "dot_runner_assignments_company_id_companies_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_run_id_heartbeat_runs_id_fk": { + "name": "dot_runner_assignments_run_id_heartbeat_runs_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_agent_id_agents_id_fk": { + "name": "dot_runner_assignments_agent_id_agents_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_operations": { + "name": "dot_runner_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "outcome": { + "name": "outcome", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_operations_request_uq": { + "name": "dot_operations_request_uq", + "columns": [ + { + "expression": "assignment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_operations_company_id_companies_id_fk": { + "name": "dot_runner_operations_company_id_companies_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_endpoints": { + "name": "email_endpoints", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receive_mode": { + "name": "receive_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_api_key_id": { + "name": "owned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_at": { + "name": "activation_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_endpoints", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_endpoints_receive_mode_check": { + "name": "email_endpoints_receive_mode_check", + "value": "\"email_endpoints\".\"receive_mode\" in ('websocket', 'webhook')" + } + }, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatic": { + "name": "automatic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_messages_provider_uq": { + "name": "email_messages_provider_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_messages_conversation_idx": { + "name": "email_messages_conversation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_messages_direction_check": { + "name": "email_messages_direction_check", + "value": "\"email_messages\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.email_sends": { + "name": "email_sends", + "schema": "", + "columns": { + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "first_attempt_at": { + "name": "first_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_sends_pending_idx": { + "name": "email_sends_pending_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"email_sends\".\"outcome\" in ('queued', 'uncertain')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_sends_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "email_sends_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_sends_outcome_check": { + "name": "email_sends_outcome_check", + "value": "\"email_sends\".\"outcome\" in ('queued', 'sent', 'delivered', 'failed', 'uncertain')" + } + }, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repositories": { + "name": "execution_workspace_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_issue_id": { + "name": "requested_by_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_identity": { + "name": "repository_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "catalog_repository_id": { + "name": "catalog_repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "relative_path": { + "name": "relative_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_ref": { + "name": "requested_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'HEAD'" + }, + "pinned_commit": { + "name": "pinned_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_keys": { + "name": "request_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_repositories_identity_idx": { + "name": "execution_workspace_repositories_identity_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_path_idx": { + "name": "execution_workspace_repositories_path_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "relative_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_request_idx": { + "name": "execution_workspace_repositories_request_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_company_workspace_idx": { + "name": "execution_workspace_repositories_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repositories_company_id_companies_id_fk": { + "name": "execution_workspace_repositories_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_requested_by_issue_id_issues_id_fk": { + "name": "execution_workspace_repositories_requested_by_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "issues", + "columnsFrom": [ + "requested_by_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_fast_responses": { + "name": "company_fast_responses", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_sponsored": { + "name": "allow_sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_fast_responses_company_id_companies_id_fk": { + "name": "company_fast_responses_company_id_companies_id_fk", + "tableFrom": "company_fast_responses", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_fast_responses_configured": { + "name": "company_fast_responses_configured", + "value": "(\"company_fast_responses\".\"connection_id\" is null) = (\"company_fast_responses\".\"grant_id\" is null) and (not \"company_fast_responses\".\"enabled\" or (\"company_fast_responses\".\"connection_id\" is not null and \"company_fast_responses\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.fast_response_requests": { + "name": "fast_response_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sponsored": { + "name": "sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "publication_status": { + "name": "publication_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_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 + }, + "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 + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "fast_response_requests_source_idx": { + "name": "fast_response_requests_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_history_idx": { + "name": "fast_response_requests_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_work_idx": { + "name": "fast_response_requests_work_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"fast_response_requests\".\"status\" in ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fast_response_requests_company_id_companies_id_fk": { + "name": "fast_response_requests_company_id_companies_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fast_response_requests_cost_event_id_cost_events_id_fk": { + "name": "fast_response_requests_cost_event_id_cost_events_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "fast_response_requests_status_check": { + "name": "fast_response_requests_status_check", + "value": "\"fast_response_requests\".\"status\" in ('pending', 'running', 'succeeded', 'failed', 'unknown', 'skipped')" + } + }, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_receipt_idx": { + "name": "finance_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_identity_context_id": { + "name": "active_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_control_deadline_at": { + "name": "execution_control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status_delivery_id": { + "name": "execution_status_delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_accounting_pending": { + "name": "cost_accounting_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_accounted_at": { + "name": "cost_accounted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_projection_version": { + "name": "accounting_projection_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_last_attempt_at": { + "name": "accounting_last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_last_error": { + "name": "accounting_last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_attempt_count": { + "name": "accounting_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "controller_lease_expires_at": { + "name": "controller_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_stage": { + "name": "execution_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_cost_accounting_pending_idx": { + "name": "heartbeat_runs_cost_accounting_pending_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_cost_data_quality_idx": { + "name": "heartbeat_runs_cost_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"finished_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true and \"heartbeat_runs\".\"status\" in ('succeeded','failed','timed_out','cancelled','interrupted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_status_delivery_idx": { + "name": "heartbeat_runs_execution_status_delivery_idx", + "columns": [ + { + "expression": "execution_status_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_status_delivery_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_control_deadline_idx": { + "name": "heartbeat_runs_execution_control_deadline_idx", + "columns": [ + { + "expression": "execution_control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_native_replacement_predecessor_uq": { + "name": "heartbeat_runs_native_replacement_predecessor_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_of_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_runs\".\"scheduled_retry_reason\" = 'native_safe_replacement'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_issue_created_idx": { + "name": "heartbeat_runs_company_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_issue_id_issues_id_fk": { + "name": "heartbeat_runs_issue_id_issues_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "heartbeat_runs_scope_binding_check": { + "name": "heartbeat_runs_scope_binding_check", + "value": "(\"heartbeat_runs\".\"scope_kind\" = 'company' AND \"heartbeat_runs\".\"issue_id\" IS NULL)\n OR \"heartbeat_runs\".\"scope_kind\" = 'issue'" + } + }, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_agent_idx": { + "name": "connection_grants_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_agent_uq": { + "name": "connection_grants_agent_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_subject_agent_id_agents_id_fk": { + "name": "connection_grants_subject_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user', 'agent')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null and \"connection_grants\".\"subject_agent_id\" is null) or (\"connection_grants\".\"kind\" = 'agent' and \"connection_grants\".\"subject_agent_id\" is not null and \"connection_grants\".\"subject_user_id\" is null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null and \"connection_grants\".\"subject_agent_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_access_grants": { + "name": "issue_access_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by_agent_id": { + "name": "granted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "issue_access_grants_subject_active_issue_idx": { + "name": "issue_access_grants_subject_active_issue_idx", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_access_grants_issue_idx": { + "name": "issue_access_grants_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_access_grants_issue_id_issues_id_fk": { + "name": "issue_access_grants_issue_id_issues_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_access_grants_granted_by_agent_id_agents_id_fk": { + "name": "issue_access_grants_granted_by_agent_id_agents_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "agents", + "columnsFrom": [ + "granted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_access_grants_subject_type_check": { + "name": "issue_access_grants_subject_type_check", + "value": "\"issue_access_grants\".\"subject_type\" in ('user', 'agent')" + }, + "issue_access_grants_source_check": { + "name": "issue_access_grants_source_check", + "value": "\"issue_access_grants\".\"source\" in ('explicit', 'assignment', 'project')" + } + }, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "originating_run_id": { + "name": "originating_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_originating_run_idx": { + "name": "issue_attachments_originating_run_idx", + "columns": [ + { + "expression": "originating_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_attachments_originating_run_id_heartbeat_runs_id_fk": { + "name": "issue_attachments_originating_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "originating_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'comment'" + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issue_comments_client_request_uq": { + "name": "issue_comments_client_request_uq", + "nullsNotDistinct": false, + "columns": [ + "issue_id", + "author_user_id", + "client_request_id" + ] + }, + "issue_comments_company_id_uq": { + "name": "issue_comments_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_comment_ids": { + "name": "origin_comment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_identity_context_id": { + "name": "source_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_agent_id": { + "name": "conversation_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_user_id": { + "name": "conversation_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_state": { + "name": "conversation_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "conversation_boundary_comment_id": { + "name": "conversation_boundary_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_root_issue_id": { + "name": "privacy_root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "privacy_parent_issue_id": { + "name": "privacy_parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_needs_generation": { + "name": "title_needs_generation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_identity_context_id": { + "name": "origin_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "continuation_identity_context_id": { + "name": "continuation_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_binding_revision": { + "name": "workspace_binding_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "workspace_selection": { + "name": "workspace_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "workspace_pending_selection": { + "name": "workspace_pending_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_conversation_identity_idx": { + "name": "issues_conversation_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_parent_idx": { + "name": "issues_company_privacy_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_parent_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_root_idx": { + "name": "issues_company_privacy_root_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_conversation_agent_id_agents_id_fk": { + "name": "issues_conversation_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "conversation_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_root_issue_id_issues_id_fk": { + "name": "issues_privacy_root_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_parent_issue_id_issues_id_fk": { + "name": "issues_privacy_parent_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "issues_visibility_check": { + "name": "issues_visibility_check", + "value": "\"issues\".\"visibility\" in ('open', 'private')" + }, + "issues_conversation_identity_check": { + "name": "issues_conversation_identity_check", + "value": "(\n \"issues\".\"conversation_agent_id\" is null and \"issues\".\"conversation_user_id\" is null and \"issues\".\"conversation_state\" is null\n ) or (\n \"issues\".\"conversation_agent_id\" is not null and \"issues\".\"conversation_user_id\" is not null\n and \"issues\".\"assignee_agent_id\" = \"issues\".\"conversation_agent_id\" and \"issues\".\"assignee_agent_id\" is not null\n and \"issues\".\"assignee_user_id\" is null and \"issues\".\"conversation_state\" is not null\n and \"issues\".\"conversation_state\" in ('active', 'waiting')\n and \"issues\".\"status\" not in ('done', 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_pid": { + "name": "controller_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "controller_process_started_at": { + "name": "controller_process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recovery_state": { + "name": "recovery_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_request_id": { + "name": "recovery_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_history": { + "name": "recovery_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "control_deadline_at": { + "name": "control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_finalizations_control_deadline_idx": { + "name": "native_run_finalizations_control_deadline_idx", + "columns": [ + { + "expression": "control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"native_run_finalizations\".\"control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_lifecycle_acknowledgments": { + "name": "plugin_lifecycle_acknowledgments", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_lifecycle_acknowledgments_event_idx": { + "name": "plugin_lifecycle_acknowledgments_event_idx", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk": { + "name": "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "resource_lifecycle_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk", + "columns": [ + "plugin_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_access_members": { + "name": "project_access_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_access_members_project_subject_uq": { + "name": "project_access_members_project_subject_uq", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_access_members_subject_lookup_idx": { + "name": "project_access_members_subject_lookup_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_access_members_company_id_companies_id_fk": { + "name": "project_access_members_company_id_companies_id_fk", + "tableFrom": "project_access_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_access_members_project_id_projects_id_fk": { + "name": "project_access_members_project_id_projects_id_fk", + "tableFrom": "project_access_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "project_access_members_subject_type_check": { + "name": "project_access_members_subject_type_check", + "value": "\"project_access_members\".\"subject_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_owner_user_id": { + "name": "privacy_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "personal_owner_user_id": { + "name": "personal_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "projects_company_personal_owner_uq": { + "name": "projects_company_personal_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "personal_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"projects\".\"personal_owner_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "projects_visibility_check": { + "name": "projects_visibility_check", + "value": "\"projects\".\"visibility\" in ('open', 'private')" + } + }, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.resource_lifecycle_events": { + "name": "resource_lifecycle_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "resource_lifecycle_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_lifecycle_events_creation_idx": { + "name": "resource_lifecycle_events_creation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"resource_lifecycle_events\".\"action\" = 'create'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_company_idx": { + "name": "resource_lifecycle_events_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_resource_order_idx": { + "name": "resource_lifecycle_events_resource_order_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_lifecycle_events_company_id_companies_id_fk": { + "name": "resource_lifecycle_events_company_id_companies_id_fk", + "tableFrom": "resource_lifecycle_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_lifecycle_events_resource_type_check": { + "name": "resource_lifecycle_events_resource_type_check", + "value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')" + }, + "resource_lifecycle_events_action_check": { + "name": "resource_lifecycle_events_action_check", + "value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'project' AND \"resource_lifecycle_events\".\"action\" IN ('update', 'archive')) OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "setup_pending": { + "name": "setup_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_webhook_delivery": { + "name": "last_webhook_delivery", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_webhook_test_receipts": { + "name": "routine_webhook_test_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_key_hash": { + "name": "delivery_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_webhook_test_receipts_delivery_uq": { + "name": "routine_webhook_test_receipts_delivery_uq", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_webhook_test_receipts_company_id_companies_id_fk": { + "name": "routine_webhook_test_receipts_company_id_companies_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk": { + "name": "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.run_identity_contexts": { + "name": "run_identity_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_context_id": { + "name": "parent_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_identity_contexts_run_revision_idx": { + "name": "run_identity_contexts_run_revision_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_run_correlation_idx": { + "name": "run_identity_contexts_run_correlation_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_company_run_idx": { + "name": "run_identity_contexts_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_identity_contexts_company_id_companies_id_fk": { + "name": "run_identity_contexts_company_id_companies_id_fk", + "tableFrom": "run_identity_contexts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runner_api_response_reservations": { + "name": "runner_api_response_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reserved_bytes": { + "name": "reserved_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "runner_api_response_reservations_company_idx": { + "name": "runner_api_response_reservations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runner_api_response_reservations_company_id_companies_id_fk": { + "name": "runner_api_response_reservations_company_id_companies_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runner_api_response_reservations_run_id_heartbeat_runs_id_fk": { + "name": "runner_api_response_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "runner_api_response_reservations_asset_id_assets_id_fk": { + "name": "runner_api_response_reservations_asset_id_assets_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_deliveries": { + "name": "tool_action_deliveries", + "schema": "", + "columns": { + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_deliveries_pending_idx": { + "name": "tool_action_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_deliveries_action_request_id_tool_action_requests_id_fk": { + "name": "tool_action_deliveries_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_company_id_companies_id_fk": { + "name": "tool_action_deliveries_company_id_companies_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_issue_id_issues_id_fk": { + "name": "tool_action_deliveries_issue_id_issues_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_snapshots": { + "name": "tool_connection_app_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toolkit": { + "name": "toolkit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accounts": { + "name": "accounts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error_at": { + "name": "error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tool_connection_app_snapshots_owner_toolkit_uq": { + "name": "tool_connection_app_snapshots_owner_toolkit_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "toolkit", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_snapshots_company_id_companies_id_fk": { + "name": "tool_connection_app_snapshots_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_snapshots_company_connection_fk": { + "name": "tool_connection_app_snapshots_company_connection_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_syncs": { + "name": "tool_connection_app_syncs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checked": { + "name": "checked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total": { + "name": "total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_app_syncs_owner_key_uq": { + "name": "tool_connection_app_syncs_owner_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_syncs_company_id_companies_id_fk": { + "name": "tool_connection_app_syncs_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_syncs_company_connection_fk": { + "name": "tool_connection_app_syncs_company_connection_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "connection_purpose": { + "name": "connection_purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_instructions": { + "name": "agent_instructions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'voice', 'runtime_auth')" + }, + "tool_connections_purpose_check": { + "name": "tool_connections_purpose_check", + "value": "\"tool_connections\".\"connection_purpose\" in ('tool', 'channel', 'ai')" + }, + "tool_connections_channel_transport_check": { + "name": "tool_connections_channel_transport_check", + "value": "(\n (\"tool_connections\".\"connection_purpose\" = 'tool' and \"tool_connections\".\"transport\" not in ('chat_sdk', 'voice', 'runtime_auth'))\n or\n (\"tool_connections\".\"connection_purpose\" = 'channel' and (\"tool_connections\".\"transport\" in ('chat_sdk', 'voice') or (\"tool_connections\".\"transport\" = 'rest_api' and \"tool_connections\".\"config\"->>'provider' = 'agentmail')))\n or\n (\"tool_connections\".\"connection_purpose\" = 'ai' and \"tool_connections\".\"transport\" = 'runtime_auth')\n )" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback', 'per_agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_expiry_idx": { + "name": "tool_mcp_gateway_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_subject_agent_idx": { + "name": "tool_oauth_states_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_subject_agent_id_agents_id_fk": { + "name": "tool_oauth_states_subject_agent_id_agents_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_company_preferences": { + "name": "user_company_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_agent_id": { + "name": "primary_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "primary_agent_initialized": { + "name": "primary_agent_initialized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_company_preferences_company_user_uq": { + "name": "user_company_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_company_preferences_primary_agent_idx": { + "name": "user_company_preferences_primary_agent_idx", + "columns": [ + { + "expression": "primary_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_company_preferences_company_id_companies_id_fk": { + "name": "user_company_preferences_company_id_companies_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_company_preferences_primary_agent_id_agents_id_fk": { + "name": "user_company_preferences_primary_agent_id_agents_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "agents", + "columnsFrom": [ + "primary_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_attachment_uploads": { + "name": "mcp_attachment_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cleaned_at": { + "name": "cleaned_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": { + "mcp_attachment_upload_request_uq": { + "name": "mcp_attachment_upload_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_attachment_upload_expiry_idx": { + "name": "mcp_attachment_upload_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_attachment_uploads_company_id_companies_id_fk": { + "name": "mcp_attachment_uploads_company_id_companies_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_attachment_uploads_user_id_user_id_fk": { + "name": "mcp_attachment_uploads_user_id_user_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_admissions": { + "name": "mcp_event_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reserves_subscription": { + "name": "reserves_subscription", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "mcp_event_admissions_expiry_idx": { + "name": "mcp_event_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_grant_idx": { + "name": "mcp_event_admissions_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_company_idx": { + "name": "mcp_event_admissions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_admissions_company_id_companies_id_fk": { + "name": "mcp_event_admissions_company_id_companies_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_deliveries": { + "name": "mcp_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mailbox_item_id": { + "name": "mailbox_item_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_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 + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "mcp_event_deliveries_mailbox_uq": { + "name": "mcp_event_deliveries_mailbox_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mailbox_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_activity_uq": { + "name": "mcp_event_deliveries_activity_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_due_idx": { + "name": "mcp_event_deliveries_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk": { + "name": "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk", + "tableFrom": "mcp_event_deliveries", + "tableTo": "mcp_event_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_delivery_source_check": { + "name": "mcp_delivery_source_check", + "value": "(\"mcp_event_deliveries\".\"activity_id\" IS NOT NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NULL) OR (\"mcp_event_deliveries\".\"activity_id\" IS NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_event_subscriptions": { + "name": "mcp_event_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_material": { + "name": "delivery_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_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 + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scanned_at": { + "name": "scanned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_event_subscriptions_expiry_idx": { + "name": "mcp_event_subscriptions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_subscriptions_company_idx": { + "name": "mcp_event_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_subscriptions_company_id_companies_id_fk": { + "name": "mcp_event_subscriptions_company_id_companies_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_subscription_resource_check": { + "name": "mcp_subscription_resource_check", + "value": "(\"mcp_event_subscriptions\".\"task_id\" IS NOT NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NULL) OR (\"mcp_event_subscriptions\".\"task_id\" IS NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_file_tickets": { + "name": "mcp_file_tickets", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "upload_id": { + "name": "upload_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_file_ticket_expiry_idx": { + "name": "mcp_file_ticket_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_file_ticket_grant_idx": { + "name": "mcp_file_ticket_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_file_tickets_company_id_companies_id_fk": { + "name": "mcp_file_tickets_company_id_companies_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk": { + "name": "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_attachment_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_mutation_receipts": { + "name": "mcp_mutation_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "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()" + } + }, + "indexes": { + "mcp_mutation_receipts_request_uq": { + "name": "mcp_mutation_receipts_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_mutation_receipts_company_idx": { + "name": "mcp_mutation_receipts_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_mutation_receipts_company_id_companies_id_fk": { + "name": "mcp_mutation_receipts_company_id_companies_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_user_id_user_id_fk": { + "name": "mcp_mutation_receipts_user_id_user_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_clients": { + "name": "mcp_oauth_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registration_source_hash": { + "name": "registration_source_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "grant_types": { + "name": "grant_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"authorization_code\",\"refresh_token\"]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_device_requests": { + "name": "mcp_oauth_device_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code_hash": { + "name": "user_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "next_poll_at": { + "name": "next_poll_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_device_code_uq": { + "name": "mcp_oauth_device_code_uq", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_user_code_uq": { + "name": "mcp_oauth_user_code_uq", + "columns": [ + { + "expression": "user_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_device_expiry_idx": { + "name": "mcp_oauth_device_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_grants": { + "name": "mcp_oauth_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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()" + } + }, + "indexes": { + "mcp_oauth_grants_user_company_idx": { + "name": "mcp_oauth_grants_user_company_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_grants_company_id_companies_id_fk": { + "name": "mcp_oauth_grants_company_id_companies_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_user_id_user_id_fk": { + "name": "mcp_oauth_grants_user_id_user_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_agent_id_agents_id_fk": { + "name": "mcp_oauth_grants_agent_id_agents_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_metadata_admissions": { + "name": "mcp_oauth_metadata_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_oauth_metadata_admissions_expiry_idx": { + "name": "mcp_oauth_metadata_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_requests": { + "name": "mcp_oauth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_requests_code_uq": { + "name": "mcp_oauth_requests_code_uq", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_requests_expiry_idx": { + "name": "mcp_oauth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_tokens": { + "name": "mcp_oauth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_tokens_hash_uq": { + "name": "mcp_oauth_tokens_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_grant_idx": { + "name": "mcp_oauth_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_expiry_idx": { + "name": "mcp_oauth_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_tokens", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": { + "public.chat_telegram_draft_ids": { + "name": "chat_telegram_draft_ids", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0337_snapshot.json b/packages/db/src/migrations/meta/0337_snapshot.json new file mode 100644 index 00000000000..ac4a041b294 --- /dev/null +++ b/packages/db/src/migrations/meta/0337_snapshot.json @@ -0,0 +1,59457 @@ +{ + "id": "65f48b10-7f94-449e-b2ce-a68f220910f3", + "prevId": "a9f48522-347d-4290-83dd-07299afb8d03", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounting_runtime_baselines": { + "name": "accounting_runtime_baselines", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounting_runtime_baselines_company_idx": { + "name": "accounting_runtime_baselines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounting_runtime_baselines_agent_id_agents_id_fk": { + "name": "accounting_runtime_baselines_agent_id_agents_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "accounting_runtime_baselines_company_id_companies_id_fk": { + "name": "accounting_runtime_baselines_company_id_companies_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.billing_invoice_lines": { + "name": "billing_invoice_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_id": { + "name": "invoice_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "billing_invoice_lines_identity_idx": { + "name": "billing_invoice_lines_identity_idx", + "columns": [ + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_invoice_lines_company_idx": { + "name": "billing_invoice_lines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoice_lines_company_id_companies_id_fk": { + "name": "billing_invoice_lines_company_id_companies_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_invoice_id_billing_invoices_id_fk": { + "name": "billing_invoice_lines_invoice_id_billing_invoices_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "billing_invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_cost_event_id_cost_events_id_fk": { + "name": "billing_invoice_lines_cost_event_id_cost_events_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_invoice_lines_amount_check": { + "name": "billing_invoice_lines_amount_check", + "value": "\"billing_invoice_lines\".\"amount_cents\" >= 0" + }, + "billing_invoice_lines_kind_check": { + "name": "billing_invoice_lines_kind_check", + "value": "\"billing_invoice_lines\".\"kind\" in ('inference', 'fee', 'credit')" + } + }, + "isRLSEnabled": false + }, + "public.billing_invoices": { + "name": "billing_invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imported_by": { + "name": "imported_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_invoices_identity_idx": { + "name": "billing_invoices_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoices_company_id_companies_id_fk": { + "name": "billing_invoices_company_id_companies_id_fk", + "tableFrom": "billing_invoices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_reservations": { + "name": "budget_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_invocation_id": { + "name": "decision_invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'held'" + }, + "provider_started_at": { + "name": "provider_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_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": { + "budget_reservations_fast_response_idx": { + "name": "budget_reservations_fast_response_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fast_response_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_decision_idx": { + "name": "budget_reservations_decision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_run_idx": { + "name": "budget_reservations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_active_idx": { + "name": "budget_reservations_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"budget_reservations\".\"state\" = 'held'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_reservations_company_id_companies_id_fk": { + "name": "budget_reservations_company_id_companies_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_run_id_heartbeat_runs_id_fk": { + "name": "budget_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_agent_id_agents_id_fk": { + "name": "budget_reservations_agent_id_agents_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_reservations_source_check": { + "name": "budget_reservations_source_check", + "value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int + (\"budget_reservations\".\"fast_response_request_id\" is not null)::int = 1" + }, + "budget_reservations_state_check": { + "name": "budget_reservations_state_check", + "value": "\"budget_reservations\".\"state\" in ('held', 'settled', 'released')" + }, + "budget_reservations_amount_check": { + "name": "budget_reservations_amount_check", + "value": "\"budget_reservations\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.cost_adjustments": { + "name": "cost_adjustments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_line_id": { + "name": "invoice_line_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_cents": { + "name": "previous_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "corrected_cents": { + "name": "corrected_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "previous_status": { + "name": "previous_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_pricing": { + "name": "previous_pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing": { + "name": "pricing", + "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": { + "cost_adjustments_receipt_idx": { + "name": "cost_adjustments_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_event_idx": { + "name": "cost_adjustments_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_invoice_line_idx": { + "name": "cost_adjustments_invoice_line_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_line_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_adjustments_company_id_companies_id_fk": { + "name": "cost_adjustments_company_id_companies_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_cost_event_id_cost_events_id_fk": { + "name": "cost_adjustments_cost_event_id_cost_events_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk": { + "name": "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "billing_invoice_lines", + "columnsFrom": [ + "invoice_line_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_adjustments_amount_check": { + "name": "cost_adjustments_amount_check", + "value": "\"cost_adjustments\".\"previous_cents\" >= 0 and \"cost_adjustments\".\"corrected_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.provider_billing_snapshots": { + "name": "provider_billing_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_billing_snapshots_identity_idx": { + "name": "provider_billing_snapshots_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_billing_snapshots_company_id_companies_id_fk": { + "name": "provider_billing_snapshots_company_id_companies_id_fk", + "tableFrom": "provider_billing_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "provider_billing_snapshots_amount_check": { + "name": "provider_billing_snapshots_amount_check", + "value": "\"provider_billing_snapshots\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.run_usage_receipts": { + "name": "run_usage_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_json": { + "name": "receipt_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_usage_receipts_source_sequence_idx": { + "name": "run_usage_receipts_source_sequence_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_usage_receipts_run_latest_idx": { + "name": "run_usage_receipts_run_latest_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_usage_receipts_company_id_companies_id_fk": { + "name": "run_usage_receipts_company_id_companies_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "run_usage_receipts_run_id_heartbeat_runs_id_fk": { + "name": "run_usage_receipts_run_id_heartbeat_runs_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "run_usage_receipts_positive_sequence": { + "name": "run_usage_receipts_positive_sequence", + "value": "\"run_usage_receipts\".\"sequence\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ai_connection": { + "name": "ai_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_grant_id": { + "name": "connection_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_method": { + "name": "connection_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_claim": { + "name": "result_claim", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commentary": { + "name": "agent_commentary", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_commentary_run_key_uq": { + "name": "agent_commentary_run_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commentary_company_kind_created_idx": { + "name": "agent_commentary_company_kind_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commentary_company_id_companies_id_fk": { + "name": "agent_commentary_company_id_companies_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_agent_id_agents_id_fk": { + "name": "agent_commentary_agent_id_agents_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_run_id_heartbeat_runs_id_fk": { + "name": "agent_commentary_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_issue_id_issues_id_fk": { + "name": "agent_commentary_issue_id_issues_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_commentary_kind_check": { + "name": "agent_commentary_kind_check", + "value": "\"agent_commentary\".\"kind\" in ('complaint', 'suggestion')" + } + }, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_identity_keys": { + "name": "agent_identity_keys", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key_pem": { + "name": "public_key_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_material": { + "name": "private_key_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_identity_keys_owner_fk": { + "name": "agent_identity_keys_owner_fk", + "tableFrom": "agent_identity_keys", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_identity_keys_algorithm_check": { + "name": "agent_identity_keys_algorithm_check", + "value": "\"agent_identity_keys\".\"algorithm\" = 'Ed25519'" + } + }, + "isRLSEnabled": false + }, + "public.agent_instruction_heads": { + "name": "agent_instruction_heads", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision_id": { + "name": "revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk": { + "name": "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk", + "tableFrom": "agent_instruction_heads", + "tableTo": "agent_instruction_revisions", + "columnsFrom": [ + "company_id", + "agent_id", + "entry_file", + "revision_id" + ], + "columnsTo": [ + "company_id", + "agent_id", + "entry_file", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_heads_identity_uq": { + "name": "agent_instruction_heads_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_revisions": { + "name": "agent_instruction_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_base64": { + "name": "content_base64", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_length": { + "name": "byte_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parent_revision_id": { + "name": "parent_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_revisions_history_idx": { + "name": "agent_instruction_revisions_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entry_file", + "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": { + "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_revisions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_revisions_identity_uq": { + "name": "agent_instruction_revisions_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_working_copies": { + "name": "agent_instruction_working_copies", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_hash": { + "name": "base_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_root": { + "name": "local_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_root": { + "name": "execution_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prepared'" + }, + "candidate_base64": { + "name": "candidate_base64", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "candidate_hash": { + "name": "candidate_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "process_stopped_at": { + "name": "process_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_copies_pending_idx": { + "name": "agent_instruction_copies_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_instruction_copies_agent_idx": { + "name": "agent_instruction_copies_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk": { + "name": "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_session_goal_actions": { + "name": "agent_session_goal_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_goal_actions_session_request_uniq": { + "name": "agent_session_goal_actions_session_request_uniq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_company_status_created_idx": { + "name": "agent_session_goal_actions_company_status_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_session_created_idx": { + "name": "agent_session_goal_actions_session_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_session_goal_actions_company_id_companies_id_fk": { + "name": "agent_session_goal_actions_company_id_companies_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_session_goal_actions_session_id_agent_task_sessions_id_fk": { + "name": "agent_session_goal_actions_session_id_agent_task_sessions_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "agent_task_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_capability_json": { + "name": "goal_capability_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_json": { + "name": "goal_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_status": { + "name": "goal_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_desired_state": { + "name": "goal_desired_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_id": { + "name": "goal_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_cursor": { + "name": "goal_source_cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "goal_revision": { + "name": "goal_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goal_observed_at": { + "name": "goal_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' OR \"agent_wakeup_requests\".\"idempotency_key\" LIKE 'interaction:%') AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_history_idx": { + "name": "agent_wakeup_requests_question_response_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'connection-intent:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_tool_action_delivery_uq": { + "name": "agent_wakeup_requests_tool_action_delivery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'tool-action-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_chat_completion_uq": { + "name": "agent_wakeup_requests_chat_completion_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'chat-completion:%'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appearance": { + "name": "appearance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "lifecycle_required_plugin_ids": { + "name": "lifecycle_required_plugin_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lifecycle_holds": { + "name": "lifecycle_holds", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "lifecycle_version": { + "name": "lifecycle_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lifecycle_error": { + "name": "lifecycle_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_operation": { + "name": "lifecycle_operation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_lifecycle_work_idx": { + "name": "agents_lifecycle_work_idx", + "columns": [ + { + "expression": "lifecycle_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_company_id_uq": { + "name": "agents_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_defaults": { + "name": "ai_connection_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_defaults_owner_method_uq": { + "name": "ai_connection_defaults_owner_method_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_defaults_company_id_companies_id_fk": { + "name": "ai_connection_defaults_company_id_companies_id_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_defaults_company_grant_fk": { + "name": "ai_connection_defaults_company_grant_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_connection_defaults_provider_check": { + "name": "ai_connection_defaults_provider_check", + "value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + }, + "ai_connection_defaults_method_check": { + "name": "ai_connection_defaults_method_check", + "value": "\"ai_connection_defaults\".\"method\" in ('subscription','api_key')" + } + }, + "isRLSEnabled": false + }, + "public.ai_connection_pools": { + "name": "ai_connection_pools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_pools_id_tool_connections_id_fk": { + "name": "ai_connection_pools_id_tool_connections_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_id_companies_id_fk": { + "name": "ai_connection_pools_company_id_companies_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_connection_fk": { + "name": "ai_connection_pools_company_connection_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_connection_pools_company_id_uq": { + "name": "ai_connection_pools_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_router_cursors": { + "name": "ai_connection_router_cursors", + "schema": "", + "columns": { + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "last_member_id": { + "name": "last_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_id_companies_id_fk": { + "name": "ai_connection_router_cursors_company_id_companies_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_pool_fk": { + "name": "ai_connection_router_cursors_company_pool_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_task_pins": { + "name": "ai_connection_task_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selection": { + "name": "selection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "member": { + "name": "member", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_task_pins_affinity_uq": { + "name": "ai_connection_task_pins_affinity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_task_pins_company_id_companies_id_fk": { + "name": "ai_connection_task_pins_company_id_companies_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_agent_id_agents_id_fk": { + "name": "ai_connection_task_pins_agent_id_agents_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_pool_fk": { + "name": "ai_connection_task_pins_company_pool_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_agent_fk": { + "name": "ai_connection_task_pins_company_agent_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_provider_defaults": { + "name": "ai_provider_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_provider_defaults_owner_provider_uq": { + "name": "ai_provider_defaults_owner_provider_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_provider_defaults_company_id_companies_id_fk": { + "name": "ai_provider_defaults_company_id_companies_id_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_provider_defaults_company_grant_fk": { + "name": "ai_provider_defaults_company_grant_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_provider_defaults_provider_check": { + "name": "ai_provider_defaults_provider_check", + "value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscription_connections": { + "name": "ai_subscription_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ai_subscription_connections_grant_idx": { + "name": "ai_subscription_connections_grant_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_credential_idx": { + "name": "ai_subscription_connections_credential_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_account_idx": { + "name": "ai_subscription_connections_account_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_connections_company_id_companies_id_fk": { + "name": "ai_subscription_connections_company_id_companies_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_connection_id_tool_connections_id_fk": { + "name": "ai_subscription_connections_connection_id_tool_connections_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_grant_id_connection_grants_id_fk": { + "name": "ai_subscription_connections_grant_id_connection_grants_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_subscription_prices": { + "name": "ai_subscription_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'month'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscription_prices_revision_idx": { + "name": "ai_subscription_prices_revision_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_prices_company_idx": { + "name": "ai_subscription_prices_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_prices_company_id_companies_id_fk": { + "name": "ai_subscription_prices_company_id_companies_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscription_prices_valid": { + "name": "ai_subscription_prices_valid", + "value": "(\"ai_subscription_prices\".\"amount_cents\" is null or \"ai_subscription_prices\".\"amount_cents\" >= 0) and \"ai_subscription_prices\".\"cadence\" in ('month','year') and \"ai_subscription_prices\".\"status\" in ('active','ended','excluded') and \"ai_subscription_prices\".\"source\" in ('catalog','user','unknown')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscriptions": { + "name": "ai_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_key": { + "name": "account_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity_verified": { + "name": "identity_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_ids": { + "name": "owner_user_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "detected_plan": { + "name": "detected_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_status": { + "name": "refresh_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_into_id": { + "name": "merged_into_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscriptions_identity_idx": { + "name": "ai_subscriptions_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscriptions_company_idx": { + "name": "ai_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscriptions_company_id_companies_id_fk": { + "name": "ai_subscriptions_company_id_companies_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk": { + "name": "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "merged_into_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscriptions_no_self_merge": { + "name": "ai_subscriptions_no_self_merge", + "value": "\"ai_subscriptions\".\"merged_into_id\" is null or \"ai_subscriptions\".\"merged_into_id\" <> \"ai_subscriptions\".\"id\"" + } + }, + "isRLSEnabled": false + }, + "public.announcement_dismissals": { + "name": "announcement_dismissals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "announcement_dismissals_user_id_announcement_id_pk": { + "name": "announcement_dismissals_user_id_announcement_id_pk", + "columns": [ + "user_id", + "announcement_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.announcement_publications": { + "name": "announcement_publications", + "schema": "", + "columns": { + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_browsers": { + "name": "browser_use_browsers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_browser_id": { + "name": "provider_browser_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "browser_use_browsers_provider_uq": { + "name": "browser_use_browsers_provider_uq", + "columns": [ + { + "expression": "provider_browser_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_browsers_session_idx": { + "name": "browser_use_browsers_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_browsers_company_id_companies_id_fk": { + "name": "browser_use_browsers_company_id_companies_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_browsers_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_browsers_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_runs": { + "name": "browser_use_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_run_id": { + "name": "provider_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "recovery_cursor": { + "name": "recovery_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'creating'" + }, + "detached_until": { + "name": "detached_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_cursor": { + "name": "event_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "events_drained": { + "name": "events_drained", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "accounted_cents": { + "name": "accounted_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress": { + "name": "progress", + "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()" + } + }, + "indexes": { + "browser_use_runs_invocation_uq": { + "name": "browser_use_runs_invocation_uq", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_provider_uq": { + "name": "browser_use_runs_provider_uq", + "columns": [ + { + "expression": "provider_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_session_idx": { + "name": "browser_use_runs_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_runs_company_id_companies_id_fk": { + "name": "browser_use_runs_company_id_companies_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_runs_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_sessions": { + "name": "browser_use_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "idle_deadline": { + "name": "idle_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_requested": { + "name": "stop_requested", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_poll_at": { + "name": "next_poll_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": { + "browser_use_sessions_task_idx": { + "name": "browser_use_sessions_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_sessions_provider_uq": { + "name": "browser_use_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_sessions_company_id_companies_id_fk": { + "name": "browser_use_sessions_company_id_companies_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_issue_id_issues_id_fk": { + "name": "browser_use_sessions_issue_id_issues_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_agent_id_agents_id_fk": { + "name": "browser_use_sessions_agent_id_agents_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_connection_id_tool_connections_id_fk": { + "name": "browser_use_sessions_connection_id_tool_connections_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_grant_id_connection_grants_id_fk": { + "name": "browser_use_sessions_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_settings": { + "name": "browser_use_settings", + "schema": "", + "columns": { + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allowed_profile_ids": { + "name": "allowed_profile_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "max_cost_usd": { + "name": "max_cost_usd", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "browser_use_settings_grant_id_connection_grants_id_fk": { + "name": "browser_use_settings_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_settings_company_id_companies_id_fk": { + "name": "browser_use_settings_company_id_companies_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" = 'open'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reservation_cents": { + "name": "reservation_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "unpriced_usage_policy": { + "name": "unpriced_usage_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'block'" + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enforcement_version": { + "name": "enforcement_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enforcement_delivered_version": { + "name": "enforcement_delivered_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_policies_reservation_nonnegative": { + "name": "budget_policies_reservation_nonnegative", + "value": "\"budget_policies\".\"reservation_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.chat_actions": { + "name": "chat_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_action_id": { + "name": "provider_action_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_actions_provider_action_uq": { + "name": "chat_actions_provider_action_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_actions_company_id_companies_id_fk": { + "name": "chat_actions_company_id_companies_id_fk", + "tableFrom": "chat_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_actions_delivery_id_chat_deliveries_id_fk": { + "name": "chat_actions_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_actions_company_delivery_fk": { + "name": "chat_actions_company_delivery_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_conversation_fk": { + "name": "chat_actions_company_conversation_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_principal_fk": { + "name": "chat_actions_company_principal_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_endpoint_fk": { + "name": "chat_actions_company_endpoint_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_agent_routes": { + "name": "chat_agent_routes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_endpoint_id": { + "name": "source_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "destination_endpoint_id": { + "name": "destination_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'explicit_mention'" + }, + "max_hops": { + "name": "max_hops", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_agent_routes_pair_uq": { + "name": "chat_agent_routes_pair_uq", + "columns": [ + { + "expression": "source_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "destination_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_agent_routes_company_id_companies_id_fk": { + "name": "chat_agent_routes_company_id_companies_id_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_source_fk": { + "name": "chat_agent_routes_company_source_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "source_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_destination_fk": { + "name": "chat_agent_routes_company_destination_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "destination_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_agent_routes_hops_check": { + "name": "chat_agent_routes_hops_check", + "value": "\"chat_agent_routes\".\"max_hops\" between 1 and 8" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_conversation_id": { + "name": "external_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_thread_id": { + "name": "external_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "external_label": { + "name": "external_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_direct_message": { + "name": "is_direct_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "communication_guidance": { + "name": "communication_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_conversations_issue_idx": { + "name": "chat_conversations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_conversations_thread_uq": { + "name": "chat_conversations_thread_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversations_company_id_companies_id_fk": { + "name": "chat_conversations_company_id_companies_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_resource_id_chat_endpoint_resources_id_fk": { + "name": "chat_conversations_resource_id_chat_endpoint_resources_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_conversations_issue_id_issues_id_fk": { + "name": "chat_conversations_issue_id_issues_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_conversations_company_issue_fk": { + "name": "chat_conversations_company_issue_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversations_company_endpoint_fk": { + "name": "chat_conversations_company_endpoint_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_company_resource_fk": { + "name": "chat_conversations_company_resource_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "company_id", + "resource_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversations_company_id_uq": { + "name": "chat_conversations_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_conversations_state_check": { + "name": "chat_conversations_state_check", + "value": "\"chat_conversations\".\"state\" in ('active', 'waiting', 'completed', 'unavailable', 'endpoint_removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_deliveries": { + "name": "chat_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_event": { + "name": "normalized_event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_deliveries_work_idx": { + "name": "chat_deliveries_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_event_uq": { + "name": "chat_deliveries_event_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_dedupe_uq": { + "name": "chat_deliveries_dedupe_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_deliveries_company_id_companies_id_fk": { + "name": "chat_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_conversation_id_chat_conversations_id_fk": { + "name": "chat_deliveries_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_principal_id_chat_external_principals_id_fk": { + "name": "chat_deliveries_principal_id_chat_external_principals_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "principal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_company_endpoint_fk": { + "name": "chat_deliveries_company_endpoint_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_company_conversation_fk": { + "name": "chat_deliveries_company_conversation_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_deliveries_company_principal_fk": { + "name": "chat_deliveries_company_principal_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_deliveries_company_id_uq": { + "name": "chat_deliveries_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_deliveries_state_check": { + "name": "chat_deliveries_state_check", + "value": "\"chat_deliveries\".\"state\" in ('received', 'filtered', 'processing', 'processed', 'retry', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoint_leases": { + "name": "chat_endpoint_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_leases_active_uq": { + "name": "chat_endpoint_leases_active_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_leases_expiry_idx": { + "name": "chat_endpoint_leases_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_leases_company_id_companies_id_fk": { + "name": "chat_endpoint_leases_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_leases_company_endpoint_fk": { + "name": "chat_endpoint_leases_company_endpoint_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_endpoint_resources": { + "name": "chat_endpoint_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_provider_resource_id": { + "name": "parent_provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "availability": { + "name": "availability", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_resources_endpoint_idx": { + "name": "chat_endpoint_resources_endpoint_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_resources_external_uq": { + "name": "chat_endpoint_resources_external_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_resources_company_id_companies_id_fk": { + "name": "chat_endpoint_resources_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_resources_company_endpoint_fk": { + "name": "chat_endpoint_resources_company_endpoint_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoint_resources_company_id_uq": { + "name": "chat_endpoint_resources_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoint_resources_availability_check": { + "name": "chat_endpoint_resources_availability_check", + "value": "\"chat_endpoint_resources\".\"availability\" in ('available', 'unavailable', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoints": { + "name": "chat_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publication_mode": { + "name": "publication_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'automatic'" + }, + "external_execution_policy": { + "name": "external_execution_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'restricted'" + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_user_id": { + "name": "sponsor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_account_label": { + "name": "provider_account_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_external_id": { + "name": "bot_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_username": { + "name": "bot_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_display_name": { + "name": "bot_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_avatar_url": { + "name": "bot_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communication_instructions": { + "name": "communication_instructions", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "require_at_mention": { + "name": "require_at_mention", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_group_chats": { + "name": "allow_group_chats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_unlinked_people": { + "name": "allow_unlinked_people", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"threads\":false,\"directMessages\":false,\"nativeStreaming\":false,\"messageEdits\":false,\"messageDeletes\":false,\"reactions\":false,\"files\":false,\"cards\":false,\"actions\":false,\"modals\":false,\"slashCommands\":false,\"ephemeralMessages\":false,\"proactiveDirectMessages\":false}'::jsonb" + }, + "setup": { + "name": "setup", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"step\":\"provider_setup\"}'::jsonb" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_publication_at": { + "name": "last_publication_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoints_company_idx": { + "name": "chat_endpoints_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agent_idx": { + "name": "chat_endpoints_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_status_idx": { + "name": "chat_endpoints_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_public_id_uq": { + "name": "chat_endpoints_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agentmail_inbox_uq": { + "name": "chat_endpoints_agentmail_inbox_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'agentmail' and \"chat_endpoints\".\"status\" != 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_connection_uq": { + "name": "chat_endpoints_connection_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_external_uq": { + "name": "chat_endpoints_live_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_discord_bot_external_uq": { + "name": "chat_endpoints_live_discord_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'discord'\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_global_app_bot_external_uq": { + "name": "chat_endpoints_live_global_app_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" in ('github', 'microsoft-teams')\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_username_uq": { + "name": "chat_endpoints_live_bot_username_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_username\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoints_company_id_companies_id_fk": { + "name": "chat_endpoints_company_id_companies_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoints_assigned_agent_id_agents_id_fk": { + "name": "chat_endpoints_assigned_agent_id_agents_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "assigned_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_endpoints_company_agent_fk": { + "name": "chat_endpoints_company_agent_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_endpoints_company_connection_fk": { + "name": "chat_endpoints_company_connection_fk", + "tableFrom": "chat_endpoints", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoints_company_id_uq": { + "name": "chat_endpoints_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoints_publication_mode_check": { + "name": "chat_endpoints_publication_mode_check", + "value": "\"chat_endpoints\".\"publication_mode\" in ('automatic', 'explicit')" + }, + "chat_endpoints_execution_policy_check": { + "name": "chat_endpoints_execution_policy_check", + "value": "\"chat_endpoints\".\"external_execution_policy\" in ('restricted', 'agent')" + }, + "chat_endpoints_email_policy_check": { + "name": "chat_endpoints_email_policy_check", + "value": "\"chat_endpoints\".\"provider\" <> 'agentmail' or (\"chat_endpoints\".\"publication_mode\" = 'explicit' and \"chat_endpoints\".\"external_execution_policy\" = 'agent')" + }, + "chat_endpoints_provider_check": { + "name": "chat_endpoints_provider_check", + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_endpoints_status_check": { + "name": "chat_endpoints_status_check", + "value": "\"chat_endpoints\".\"status\" in ('draft', 'verifying', 'active', 'paused', 'attention', 'revoked', 'archived')" + }, + "chat_endpoints_deployment_check": { + "name": "chat_endpoints_deployment_check", + "value": "\"chat_endpoints\".\"deployment_mode\" in ('direct', 'relay')" + }, + "chat_endpoints_concurrency_check": { + "name": "chat_endpoints_concurrency_check", + "value": "\"chat_endpoints\".\"concurrency_policy\" in ('burst', 'queue', 'debounce', 'drop', 'concurrent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_external_principals": { + "name": "chat_external_principals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_external_principals_company_idx": { + "name": "chat_external_principals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_external_principals_external_uq": { + "name": "chat_external_principals_external_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_external_principals_company_id_companies_id_fk": { + "name": "chat_external_principals_company_id_companies_id_fk", + "tableFrom": "chat_external_principals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_external_principals_company_id_uq": { + "name": "chat_external_principals_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_external_principals_provider_check": { + "name": "chat_external_principals_provider_check", + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_external_principals_kind_check": { + "name": "chat_external_principals_kind_check", + "value": "\"chat_external_principals\".\"kind\" in ('user', 'bot', 'app', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.chat_identity_links": { + "name": "chat_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paperclip_user_id": { + "name": "paperclip_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "confirmation_token_hash": { + "name": "confirmation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_identity_links_user_idx": { + "name": "chat_identity_links_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paperclip_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_identity_links_endpoint_principal_uq": { + "name": "chat_identity_links_endpoint_principal_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_identity_links_company_id_companies_id_fk": { + "name": "chat_identity_links_company_id_companies_id_fk", + "tableFrom": "chat_identity_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_endpoint_fk": { + "name": "chat_identity_links_company_endpoint_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_principal_fk": { + "name": "chat_identity_links_company_principal_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_identity_links_status_check": { + "name": "chat_identity_links_status_check", + "value": "\"chat_identity_links\".\"status\" in ('pending', 'linked', 'revoked', 'expired')" + } + }, + "isRLSEnabled": false + }, + "public.chat_message_links": { + "name": "chat_message_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_message_links_provider_message_uq": { + "name": "chat_message_links_provider_message_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_links_company_id_companies_id_fk": { + "name": "chat_message_links_company_id_companies_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_delivery_id_chat_deliveries_id_fk": { + "name": "chat_message_links_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_publication_id_chat_publications_id_fk": { + "name": "chat_message_links_publication_id_chat_publications_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_comment_id_issue_comments_id_fk": { + "name": "chat_message_links_comment_id_issue_comments_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_company_endpoint_fk": { + "name": "chat_message_links_company_endpoint_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_company_delivery_fk": { + "name": "chat_message_links_company_delivery_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_publication_fk": { + "name": "chat_message_links_company_publication_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_comment_fk": { + "name": "chat_message_links_company_comment_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_conversation_fk": { + "name": "chat_message_links_company_conversation_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_message_links_direction_check": { + "name": "chat_message_links_direction_check", + "value": "\"chat_message_links\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.chat_publications": { + "name": "chat_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_publications_company_id_uq": { + "name": "chat_publications_company_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_work_idx": { + "name": "chat_publications_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_idempotency_uq": { + "name": "chat_publications_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_publications_company_id_companies_id_fk": { + "name": "chat_publications_company_id_companies_id_fk", + "tableFrom": "chat_publications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_issue_id_issues_id_fk": { + "name": "chat_publications_issue_id_issues_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_publications_comment_id_issue_comments_id_fk": { + "name": "chat_publications_comment_id_issue_comments_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_publications_company_issue_fk": { + "name": "chat_publications_company_issue_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_comment_fk": { + "name": "chat_publications_company_comment_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_endpoint_fk": { + "name": "chat_publications_company_endpoint_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_company_conversation_fk": { + "name": "chat_publications_company_conversation_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_publications_state_check": { + "name": "chat_publications_state_check", + "value": "\"chat_publications\".\"state\" in ('pending', 'streaming', 'published', 'retry', 'delivery_unknown', 'failed', 'cancelled', 'awaiting_consent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_sdk_state": { + "name": "chat_sdk_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sdk_state_key_uq": { + "name": "chat_sdk_state_key_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sdk_state_expiry_idx": { + "name": "chat_sdk_state_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sdk_state_company_id_companies_id_fk": { + "name": "chat_sdk_state_company_id_companies_id_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sdk_state_company_endpoint_fk": { + "name": "chat_sdk_state_company_endpoint_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_completion_deliveries": { + "name": "chat_completion_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status_version": { + "name": "status_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response_comment_id": { + "name": "response_comment_id", + "type": "uuid", + "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": { + "chat_completion_deliveries_transition_uq": { + "name": "chat_completion_deliveries_transition_uq", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_completion_deliveries_pending_idx": { + "name": "chat_completion_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_completion_deliveries_company_id_companies_id_fk": { + "name": "chat_completion_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk": { + "name": "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "chat_task_handoffs", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "task_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_completion_deliveries_response_comment_id_issue_comments_id_fk": { + "name": "chat_completion_deliveries_response_comment_id_issue_comments_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "issue_comments", + "columnsFrom": [ + "response_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_task_handoffs": { + "name": "chat_task_handoffs", + "schema": "", + "columns": { + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_task_handoffs_source_idx": { + "name": "chat_task_handoffs_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_task_handoffs_task_id_issues_id_fk": { + "name": "chat_task_handoffs_task_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_company_id_companies_id_fk": { + "name": "chat_task_handoffs_company_id_companies_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_conversation_id_issues_id_fk": { + "name": "chat_task_handoffs_conversation_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_agent_id_agents_id_fk": { + "name": "chat_task_handoffs_agent_id_agents_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_discord_command_owners": { + "name": "chat_discord_command_owners", + "schema": "", + "columns": { + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_id": { + "name": "action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_discord_command_owners_application_check": { + "name": "chat_discord_command_owners_application_check", + "value": "\"chat_discord_command_owners\".\"application_id\" ~ '^[1-9][0-9]{16,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_configurations": { + "name": "chat_github_configurations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_configurations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_config_revision_check": { + "name": "chat_github_config_revision_check", + "value": "\"chat_github_configurations\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_registrations": { + "name": "chat_github_registrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trusted_origin": { + "name": "trusted_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "owner_login": { + "name": "owner_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handoff": { + "name": "handoff", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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": { + "chat_github_registration_state_uq": { + "name": "chat_github_registration_state_uq", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_registration_endpoint_idx": { + "name": "chat_github_registration_endpoint_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_registration_status_check": { + "name": "chat_github_registration_status_check", + "value": "\"chat_github_registrations\".\"status\" in ('pending', 'exchanging', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_reviews": { + "name": "chat_github_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pull_number": { + "name": "pull_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration_revision": { + "name": "configuration_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "policy_snapshot": { + "name": "policy_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "assessment": { + "name": "assessment", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "conclusion": { + "name": "conclusion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_url": { + "name": "check_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_id": { + "name": "summary_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_url": { + "name": "summary_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publication_receipts": { + "name": "publication_receipts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_github_reviews_delivery_uq": { + "name": "chat_github_reviews_delivery_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_pull_idx": { + "name": "chat_github_reviews_pull_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pull_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_task_idx": { + "name": "chat_github_reviews_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_reviews_run_id_heartbeat_runs_id_fk": { + "name": "chat_github_reviews_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk": { + "name": "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_github_reviews_company_id_uq": { + "name": "chat_github_reviews_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_github_review_pull_number_check": { + "name": "chat_github_review_pull_number_check", + "value": "\"chat_github_reviews\".\"pull_number\" > 0" + }, + "chat_github_review_state_check": { + "name": "chat_github_review_state_check", + "value": "\"chat_github_reviews\".\"state\" in ('queued', 'running', 'completed', 'incomplete', 'error', 'superseded', 'manual_required')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_manager_grants": { + "name": "chat_slack_manager_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_app_id": { + "name": "manager_app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "access_secret_id": { + "name": "access_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_secret_id": { + "name": "refresh_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rate_limits": { + "name": "rate_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_slack_manager_grants_company_id_idx": { + "name": "chat_slack_manager_grants_company_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_slack_manager_grants_owner_idx": { + "name": "chat_slack_manager_grants_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manager_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_slack_manager_grants_company_id_companies_id_fk": { + "name": "chat_slack_manager_grants_company_id_companies_id_fk", + "tableFrom": "chat_slack_manager_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_manager_grants_status_check": { + "name": "chat_slack_manager_grants_status_check", + "value": "\"chat_slack_manager_grants\".\"status\" in ('active', 'revoked', 'reauthorize')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_registrations": { + "name": "chat_slack_registrations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "manager_grant_id": { + "name": "manager_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "manifest_hash": { + "name": "manifest_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "callback_uri": { + "name": "callback_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_ids": { + "name": "secret_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_slack_manager_grants", + "columnsFrom": [ + "company_id", + "manager_grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_registration_status_check": { + "name": "chat_slack_registration_status_check", + "value": "\"chat_slack_registrations\".\"status\" in ('creating', 'uncertain', 'failed', 'install', 'credentials_saved', 'configured', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_teams_file_transfers": { + "name": "chat_teams_file_transfers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "authorized_user_id": { + "name": "authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_generation": { + "name": "runtime_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_generation": { + "name": "conversation_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_digest": { + "name": "source_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authority_digest": { + "name": "authority_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aad_object_id": { + "name": "aad_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_sha256": { + "name": "token_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'consent_pending'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_expires_at": { + "name": "attempt_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_message_id": { + "name": "consent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_info_message_id": { + "name": "file_info_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_activity_id": { + "name": "response_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_digest": { + "name": "response_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_state": { + "name": "private_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_teams_file_transfers_publication_uq": { + "name": "chat_teams_file_transfers_publication_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_token_uq": { + "name": "chat_teams_file_transfers_token_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_work_idx": { + "name": "chat_teams_file_transfers_work_idx", + "columns": [ + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_teams_file_transfers_company_id_companies_id_fk": { + "name": "chat_teams_file_transfers_company_id_companies_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_issue_id_issues_id_fk": { + "name": "chat_teams_file_transfers_issue_id_issues_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_teams_file_transfers_phase_check": { + "name": "chat_teams_file_transfers_phase_check", + "value": "\"chat_teams_file_transfers\".\"phase\" in ('consent_pending','consent_sending','consent_unknown','awaiting_consent','upload_pending','uploading','upload_unknown','file_info_pending','file_info_sending','file_info_unknown','delivered','declined','expired','cancelled','conflict')" + }, + "chat_teams_file_transfers_bounds_check": { + "name": "chat_teams_file_transfers_bounds_check", + "value": "\"chat_teams_file_transfers\".\"version\" > 0 and \"chat_teams_file_transfers\".\"runtime_generation\" >= 0 and \"chat_teams_file_transfers\".\"conversation_generation\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" < 62914560" + }, + "chat_teams_file_transfers_hash_check": { + "name": "chat_teams_file_transfers_hash_check", + "value": "\"chat_teams_file_transfers\".\"source_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"authority_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"sha256\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"token_sha256\" ~ '^[a-f0-9]{64}$'" + }, + "chat_teams_file_transfers_attempt_check": { + "name": "chat_teams_file_transfers_attempt_check", + "value": "(\"chat_teams_file_transfers\".\"attempt_id\" is null) = (\"chat_teams_file_transfers\".\"attempt_expires_at\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_callbacks": { + "name": "chat_voice_callbacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_callbacks_owner_uq": { + "name": "chat_voice_callbacks_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_callbacks_endpoint_fk": { + "name": "chat_voice_callbacks_endpoint_fk", + "tableFrom": "chat_voice_callbacks", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_callbacks_phone_check": { + "name": "chat_voice_callbacks_phone_check", + "value": "\"chat_voice_callbacks\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_inbound_calls": { + "name": "chat_voice_inbound_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "approval_code": { + "name": "approval_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "intake_issue_id": { + "name": "intake_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_issue_id": { + "name": "requested_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_inbound_expiry_idx": { + "name": "chat_voice_inbound_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_inbound_endpoint_fk": { + "name": "chat_voice_inbound_endpoint_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_inbound_session_fk": { + "name": "chat_voice_inbound_session_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_intake_fk": { + "name": "chat_voice_inbound_intake_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "intake_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_requested_issue_fk": { + "name": "chat_voice_inbound_requested_issue_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "requested_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_inbound_provider_uq": { + "name": "chat_voice_inbound_provider_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_inbound_state_check": { + "name": "chat_voice_inbound_state_check", + "value": "\"chat_voice_inbound_calls\".\"state\" in ('guest_intake', 'awaiting_approval', 'approving', 'approved', 'denied', 'expired', 'ended')" + }, + "chat_voice_inbound_token_check": { + "name": "chat_voice_inbound_token_check", + "value": "\"chat_voice_inbound_calls\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_inbound_code_check": { + "name": "chat_voice_inbound_code_check", + "value": "\"chat_voice_inbound_calls\".\"approval_code\" ~ '^[0-9]{6}$'" + }, + "chat_voice_inbound_generation_check": { + "name": "chat_voice_inbound_generation_check", + "value": "\"chat_voice_inbound_calls\".\"generation\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_phone_lines": { + "name": "chat_voice_phone_lines", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_number_id": { + "name": "provider_number_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest_intake": { + "name": "guest_intake", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "low_trust_environment_id": { + "name": "low_trust_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk": { + "name": "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "environments", + "columnsFrom": [ + "low_trust_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_phone_lines_endpoint_fk": { + "name": "chat_voice_phone_lines_endpoint_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_phone_lines_number_uq": { + "name": "chat_voice_phone_lines_number_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_number_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_phone_lines_number_check": { + "name": "chat_voice_phone_lines_number_check", + "value": "\"chat_voice_phone_lines\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_reports": { + "name": "chat_voice_reports", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "transcript": { + "name": "transcript", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cost_micro_usd": { + "name": "cost_micro_usd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_updated_at": { + "name": "provider_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_check_at": { + "name": "next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_reports_pending_idx": { + "name": "chat_voice_reports_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_reports_session_fk": { + "name": "chat_voice_reports_session_fk", + "tableFrom": "chat_voice_reports", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_reports_status_check": { + "name": "chat_voice_reports_status_check", + "value": "\"chat_voice_reports\".\"status\" in ('pending', 'available', 'unavailable')" + }, + "chat_voice_reports_cost_check": { + "name": "chat_voice_reports_cost_check", + "value": "\"chat_voice_reports\".\"cost_micro_usd\" is null or \"chat_voice_reports\".\"cost_micro_usd\" ~ '^[0-9]{1,30}$'" + }, + "chat_voice_reports_duration_check": { + "name": "chat_voice_reports_duration_check", + "value": "\"chat_voice_reports\".\"duration_seconds\" is null or \"chat_voice_reports\".\"duration_seconds\" >= 0" + }, + "chat_voice_reports_attempts_check": { + "name": "chat_voice_reports_attempts_check", + "value": "\"chat_voice_reports\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_replies": { + "name": "chat_voice_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "spoken_at": { + "name": "spoken_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": { + "chat_voice_replies_publication_uq": { + "name": "chat_voice_replies_publication_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_replies_cursor_uq": { + "name": "chat_voice_replies_cursor_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cursor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_replies_company_id_companies_id_fk": { + "name": "chat_voice_replies_company_id_companies_id_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_replies_session_fk": { + "name": "chat_voice_replies_session_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_replies_publication_fk": { + "name": "chat_voice_replies_publication_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_replies_cursor_check": { + "name": "chat_voice_replies_cursor_check", + "value": "\"chat_voice_replies\".\"cursor\" > 0" + }, + "chat_voice_replies_spoken_check": { + "name": "chat_voice_replies_spoken_check", + "value": "\"chat_voice_replies\".\"spoken_at\" is null or \"chat_voice_replies\".\"delivered_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_sessions": { + "name": "chat_voice_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_id": { + "name": "caller_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reply_cursor": { + "name": "reply_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_sessions_provider_uq": { + "name": "chat_voice_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_request_uq": { + "name": "chat_voice_sessions_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_live_caller_task_uq": { + "name": "chat_voice_sessions_live_caller_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_voice_sessions\".\"state\" not in ('ended', 'failed', 'expired')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_expiry_idx": { + "name": "chat_voice_sessions_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_sessions_company_id_companies_id_fk": { + "name": "chat_voice_sessions_company_id_companies_id_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_sessions_endpoint_fk": { + "name": "chat_voice_sessions_endpoint_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_conversation_fk": { + "name": "chat_voice_sessions_conversation_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_issue_fk": { + "name": "chat_voice_sessions_issue_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_agent_fk": { + "name": "chat_voice_sessions_agent_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_sessions_company_id_uq": { + "name": "chat_voice_sessions_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_sessions_state_check": { + "name": "chat_voice_sessions_state_check", + "value": "\"chat_voice_sessions\".\"state\" in ('reserved', 'creating', 'creation_unknown', 'connecting', 'active', 'awaiting_approval', 'ending', 'ended', 'failed', 'expired')" + }, + "chat_voice_sessions_mode_check": { + "name": "chat_voice_sessions_mode_check", + "value": "\"chat_voice_sessions\".\"mode\" in ('browser', 'inbound_phone', 'outbound_phone')" + }, + "chat_voice_sessions_authority_check": { + "name": "chat_voice_sessions_authority_check", + "value": "\"chat_voice_sessions\".\"caller_authority\" in ('member', 'instance_admin', 'local_board', 'guest_intake', 'pending_approval')" + }, + "chat_voice_sessions_counters_check": { + "name": "chat_voice_sessions_counters_check", + "value": "\"chat_voice_sessions\".\"generation\" >= 1 and \"chat_voice_sessions\".\"reply_cursor\" >= 0" + }, + "chat_voice_sessions_token_check": { + "name": "chat_voice_sessions_token_check", + "value": "\"chat_voice_sessions\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_sessions_expiry_check": { + "name": "chat_voice_sessions_expiry_check", + "value": "\"chat_voice_sessions\".\"expires_at\" > \"chat_voice_sessions\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_tool_calls": { + "name": "chat_voice_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_tool_call_id": { + "name": "provider_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_tool_calls_tool_uq": { + "name": "chat_voice_tool_calls_tool_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_tool_calls_webhook_uq": { + "name": "chat_voice_tool_calls_webhook_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_tool_calls_company_id_companies_id_fk": { + "name": "chat_voice_tool_calls_company_id_companies_id_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_session_fk": { + "name": "chat_voice_tool_calls_session_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_delivery_fk": { + "name": "chat_voice_tool_calls_delivery_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_tool_calls_tool_check": { + "name": "chat_voice_tool_calls_tool_check", + "value": "\"chat_voice_tool_calls\".\"tool\" in ('submit_request', 'get_updates', 'answer_question')" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ai_session_epoch": { + "name": "ai_session_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_source_entries": { + "name": "company_skill_source_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inspection": { + "name": "inspection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "selection": { + "name": "selection", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "present": { + "name": "present", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_skill_source_entries_source_path_idx": { + "name": "company_skill_source_entries_source_path_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_source_entries_company_skill_idx": { + "name": "company_skill_source_entries_company_skill_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_source_entries_company_id_companies_id_fk": { + "name": "company_skill_source_entries_company_id_companies_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_source_id_company_skill_sources_id_fk": { + "name": "company_skill_source_entries_source_id_company_skill_sources_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skill_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_skill_id_company_skills_id_fk": { + "name": "company_skill_source_entries_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_sources": { + "name": "company_skill_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_url": { + "name": "repository_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_ref": { + "name": "tracking_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "package_mode": { + "name": "package_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skills'" + }, + "repository_package": { + "name": "repository_package", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_folders": { + "name": "excluded_folders", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_scan_commit": { + "name": "last_scan_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_sources_repository_ref_idx": { + "name": "company_skill_sources_repository_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_sources_identity_ref_idx": { + "name": "company_skill_sources_identity_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_sources_company_id_companies_id_fk": { + "name": "company_skill_sources_company_id_companies_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_sources_connection_id_tool_connections_id_fk": { + "name": "company_skill_sources_connection_id_tool_connections_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_repository_snapshots": { + "name": "company_skill_repository_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_repository_snapshots_content_idx": { + "name": "company_skill_repository_snapshots_content_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_repository_snapshots_company_id_companies_id_fk": { + "name": "company_skill_repository_snapshots_company_id_companies_id_fk", + "tableFrom": "company_skill_repository_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_snapshot_id": { + "name": "repository_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_skill_path": { + "name": "repository_skill_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk": { + "name": "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skill_repository_snapshots", + "columnsFrom": [ + "repository_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_event_deliveries": { + "name": "connection_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_delivery_id": { + "name": "provider_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_payload": { + "name": "normalized_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_event_deliveries_company_provider_id_uq": { + "name": "connection_event_deliveries_company_provider_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_event_deliveries_company_status_idx": { + "name": "connection_event_deliveries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_event_deliveries_company_id_companies_id_fk": { + "name": "connection_event_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_event_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_intent_deliveries": { + "name": "connection_intent_deliveries", + "schema": "", + "columns": { + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_intent_deliveries_pending_idx": { + "name": "connection_intent_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_intent_deliveries_company_id_companies_id_fk": { + "name": "connection_intent_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_kind": { + "name": "usage_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_cost_cents": { + "name": "reported_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "pricing_provenance": { + "name": "pricing_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_receipt_idx": { + "name": "cost_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_provider_request_idx": { + "name": "cost_events_provider_request_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_project_occurred_idx": { + "name": "cost_events_company_project_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_unpriced_idx": { + "name": "cost_events_unpriced_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_data_quality_idx": { + "name": "cost_events_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"billing_type\" = 'unknown' or (\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_subscription_occurred_idx": { + "name": "cost_events_company_subscription_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_subscription_id_ai_subscriptions_id_fk": { + "name": "cost_events_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "cost_events", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_events_usage_kind_check": { + "name": "cost_events_usage_kind_check", + "value": "\"cost_events\".\"usage_kind\" in ('decision', 'fast_response') or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)" + }, + "cost_events_nonnegative_amounts": { + "name": "cost_events_nonnegative_amounts", + "value": "\"cost_events\".\"cost_cents\" >= 0 and \"cost_events\".\"input_tokens\" >= 0 and \"cost_events\".\"cached_input_tokens\" >= 0 and \"cost_events\".\"output_tokens\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.company_decision_models": { + "name": "company_decision_models", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_background": { + "name": "allow_background", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_decision_models_company_id_companies_id_fk": { + "name": "company_decision_models_company_id_companies_id_fk", + "tableFrom": "company_decision_models", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_decisions_configured": { + "name": "company_decisions_configured", + "value": "(\"company_decision_models\".\"connection_id\" is null) = (\"company_decision_models\".\"grant_id\" is null) and (not \"company_decision_models\".\"enabled\" or (\"company_decision_models\".\"connection_id\" is not null and \"company_decision_models\".\"provider\" is not null and \"company_decision_models\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.decision_invocations": { + "name": "decision_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "identity_context_id": { + "name": "identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "question_types": { + "name": "question_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_invocations_company_started_idx": { + "name": "decision_invocations_company_started_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "decision_invocations_pending_idx": { + "name": "decision_invocations_pending_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"decision_invocations\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_invocations_cost_idx": { + "name": "decision_invocations_cost_idx", + "columns": [ + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_invocations_company_id_companies_id_fk": { + "name": "decision_invocations_company_id_companies_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_invocations_cost_event_id_cost_events_id_fk": { + "name": "decision_invocations_cost_event_id_cost_events_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_invocations_status_check": { + "name": "decision_invocations_status_check", + "value": "\"decision_invocations\".\"status\" in ('running', 'succeeded', 'failed', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_agent_bindings": { + "name": "dot_agent_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operator_id": { + "name": "operator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pairing'" + }, + "pairing_code_hash": { + "name": "pairing_code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pairing_expires_at": { + "name": "pairing_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dot_url": { + "name": "dot_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_hash": { + "name": "challenge_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_expires_at": { + "name": "challenge_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ready_at": { + "name": "ready_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_bindings_active_agent_uq": { + "name": "dot_bindings_active_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_active_grant_uq": { + "name": "dot_bindings_active_grant_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL AND \"dot_agent_bindings\".\"grant_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_pairing_code_uq": { + "name": "dot_bindings_pairing_code_uq", + "columns": [ + { + "expression": "pairing_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_agent_bindings_company_id_companies_id_fk": { + "name": "dot_agent_bindings_company_id_companies_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_agent_id_agents_id_fk": { + "name": "dot_agent_bindings_agent_id_agents_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk": { + "name": "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_mailbox_items": { + "name": "dot_mailbox_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "references": { + "name": "references", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_mailbox_event_uq": { + "name": "dot_mailbox_event_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_mailbox_binding_cursor_idx": { + "name": "dot_mailbox_binding_cursor_idx", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_mailbox_items_company_id_companies_id_fk": { + "name": "dot_mailbox_items_company_id_companies_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_assignments": { + "name": "dot_runner_assignments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "normalized_session_id": { + "name": "normalized_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "catalog_digest": { + "name": "catalog_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projection": { + "name": "projection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "accept_by": { + "name": "accept_by", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_activity_at": { + "name": "last_activity_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()" + } + }, + "indexes": { + "dot_assignments_turn_uq": { + "name": "dot_assignments_turn_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_active_binding_uq": { + "name": "dot_assignments_active_binding_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_runner_assignments\".\"status\" IN ('offered', 'accepted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_company_idx": { + "name": "dot_assignments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_assignments_company_id_companies_id_fk": { + "name": "dot_runner_assignments_company_id_companies_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_run_id_heartbeat_runs_id_fk": { + "name": "dot_runner_assignments_run_id_heartbeat_runs_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_agent_id_agents_id_fk": { + "name": "dot_runner_assignments_agent_id_agents_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_operations": { + "name": "dot_runner_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "outcome": { + "name": "outcome", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_operations_request_uq": { + "name": "dot_operations_request_uq", + "columns": [ + { + "expression": "assignment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_operations_company_id_companies_id_fk": { + "name": "dot_runner_operations_company_id_companies_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_endpoints": { + "name": "email_endpoints", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receive_mode": { + "name": "receive_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_api_key_id": { + "name": "owned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_at": { + "name": "activation_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_endpoints", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_endpoints_receive_mode_check": { + "name": "email_endpoints_receive_mode_check", + "value": "\"email_endpoints\".\"receive_mode\" in ('websocket', 'webhook')" + } + }, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatic": { + "name": "automatic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_messages_provider_uq": { + "name": "email_messages_provider_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_messages_conversation_idx": { + "name": "email_messages_conversation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_messages_direction_check": { + "name": "email_messages_direction_check", + "value": "\"email_messages\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.email_sends": { + "name": "email_sends", + "schema": "", + "columns": { + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "first_attempt_at": { + "name": "first_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_sends_pending_idx": { + "name": "email_sends_pending_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"email_sends\".\"outcome\" in ('queued', 'uncertain')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_sends_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "email_sends_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_sends_outcome_check": { + "name": "email_sends_outcome_check", + "value": "\"email_sends\".\"outcome\" in ('queued', 'sent', 'delivered', 'failed', 'uncertain')" + } + }, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repositories": { + "name": "execution_workspace_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_issue_id": { + "name": "requested_by_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_identity": { + "name": "repository_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "catalog_repository_id": { + "name": "catalog_repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "relative_path": { + "name": "relative_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_ref": { + "name": "requested_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'HEAD'" + }, + "pinned_commit": { + "name": "pinned_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_keys": { + "name": "request_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_repositories_identity_idx": { + "name": "execution_workspace_repositories_identity_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_path_idx": { + "name": "execution_workspace_repositories_path_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "relative_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_request_idx": { + "name": "execution_workspace_repositories_request_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_company_workspace_idx": { + "name": "execution_workspace_repositories_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repositories_company_id_companies_id_fk": { + "name": "execution_workspace_repositories_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_requested_by_issue_id_issues_id_fk": { + "name": "execution_workspace_repositories_requested_by_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "issues", + "columnsFrom": [ + "requested_by_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repository_roots": { + "name": "execution_workspace_repository_roots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_cwd": { + "name": "canonical_cwd", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "root_key": { + "name": "root_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "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": { + "execution_workspace_repository_roots_company_root_idx": { + "name": "execution_workspace_repository_roots_company_root_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repository_roots_company_id_companies_id_fk": { + "name": "execution_workspace_repository_roots_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repository_roots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_fast_responses": { + "name": "company_fast_responses", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_sponsored": { + "name": "allow_sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_fast_responses_company_id_companies_id_fk": { + "name": "company_fast_responses_company_id_companies_id_fk", + "tableFrom": "company_fast_responses", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_fast_responses_configured": { + "name": "company_fast_responses_configured", + "value": "(\"company_fast_responses\".\"connection_id\" is null) = (\"company_fast_responses\".\"grant_id\" is null) and (not \"company_fast_responses\".\"enabled\" or (\"company_fast_responses\".\"connection_id\" is not null and \"company_fast_responses\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.fast_response_requests": { + "name": "fast_response_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sponsored": { + "name": "sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "publication_status": { + "name": "publication_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_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 + }, + "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 + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "fast_response_requests_source_idx": { + "name": "fast_response_requests_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_history_idx": { + "name": "fast_response_requests_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_work_idx": { + "name": "fast_response_requests_work_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"fast_response_requests\".\"status\" in ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fast_response_requests_company_id_companies_id_fk": { + "name": "fast_response_requests_company_id_companies_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fast_response_requests_cost_event_id_cost_events_id_fk": { + "name": "fast_response_requests_cost_event_id_cost_events_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "fast_response_requests_status_check": { + "name": "fast_response_requests_status_check", + "value": "\"fast_response_requests\".\"status\" in ('pending', 'running', 'succeeded', 'failed', 'unknown', 'skipped')" + } + }, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_receipt_idx": { + "name": "finance_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_identity_context_id": { + "name": "active_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_control_deadline_at": { + "name": "execution_control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status_delivery_id": { + "name": "execution_status_delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_accounting_pending": { + "name": "cost_accounting_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_accounted_at": { + "name": "cost_accounted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_projection_version": { + "name": "accounting_projection_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_last_attempt_at": { + "name": "accounting_last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_last_error": { + "name": "accounting_last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_attempt_count": { + "name": "accounting_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "controller_lease_expires_at": { + "name": "controller_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_stage": { + "name": "execution_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_cost_accounting_pending_idx": { + "name": "heartbeat_runs_cost_accounting_pending_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_cost_data_quality_idx": { + "name": "heartbeat_runs_cost_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"finished_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true and \"heartbeat_runs\".\"status\" in ('succeeded','failed','timed_out','cancelled','interrupted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_status_delivery_idx": { + "name": "heartbeat_runs_execution_status_delivery_idx", + "columns": [ + { + "expression": "execution_status_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_status_delivery_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_control_deadline_idx": { + "name": "heartbeat_runs_execution_control_deadline_idx", + "columns": [ + { + "expression": "execution_control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_native_replacement_predecessor_uq": { + "name": "heartbeat_runs_native_replacement_predecessor_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_of_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_runs\".\"scheduled_retry_reason\" = 'native_safe_replacement'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_issue_created_idx": { + "name": "heartbeat_runs_company_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_issue_id_issues_id_fk": { + "name": "heartbeat_runs_issue_id_issues_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "heartbeat_runs_scope_binding_check": { + "name": "heartbeat_runs_scope_binding_check", + "value": "(\"heartbeat_runs\".\"scope_kind\" = 'company' AND \"heartbeat_runs\".\"issue_id\" IS NULL)\n OR \"heartbeat_runs\".\"scope_kind\" = 'issue'" + } + }, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_agent_idx": { + "name": "connection_grants_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_agent_uq": { + "name": "connection_grants_agent_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_subject_agent_id_agents_id_fk": { + "name": "connection_grants_subject_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user', 'agent')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null and \"connection_grants\".\"subject_agent_id\" is null) or (\"connection_grants\".\"kind\" = 'agent' and \"connection_grants\".\"subject_agent_id\" is not null and \"connection_grants\".\"subject_user_id\" is null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null and \"connection_grants\".\"subject_agent_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_access_grants": { + "name": "issue_access_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by_agent_id": { + "name": "granted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "issue_access_grants_subject_active_issue_idx": { + "name": "issue_access_grants_subject_active_issue_idx", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_access_grants_issue_idx": { + "name": "issue_access_grants_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_access_grants_issue_id_issues_id_fk": { + "name": "issue_access_grants_issue_id_issues_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_access_grants_granted_by_agent_id_agents_id_fk": { + "name": "issue_access_grants_granted_by_agent_id_agents_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "agents", + "columnsFrom": [ + "granted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_access_grants_subject_type_check": { + "name": "issue_access_grants_subject_type_check", + "value": "\"issue_access_grants\".\"subject_type\" in ('user', 'agent')" + }, + "issue_access_grants_source_check": { + "name": "issue_access_grants_source_check", + "value": "\"issue_access_grants\".\"source\" in ('explicit', 'assignment', 'project')" + } + }, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "originating_run_id": { + "name": "originating_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_originating_run_idx": { + "name": "issue_attachments_originating_run_idx", + "columns": [ + { + "expression": "originating_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_attachments_originating_run_id_heartbeat_runs_id_fk": { + "name": "issue_attachments_originating_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "originating_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'comment'" + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issue_comments_client_request_uq": { + "name": "issue_comments_client_request_uq", + "nullsNotDistinct": false, + "columns": [ + "issue_id", + "author_user_id", + "client_request_id" + ] + }, + "issue_comments_company_id_uq": { + "name": "issue_comments_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_comment_ids": { + "name": "origin_comment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_identity_context_id": { + "name": "source_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_agent_id": { + "name": "conversation_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_user_id": { + "name": "conversation_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_state": { + "name": "conversation_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "conversation_boundary_comment_id": { + "name": "conversation_boundary_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_root_issue_id": { + "name": "privacy_root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "privacy_parent_issue_id": { + "name": "privacy_parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_needs_generation": { + "name": "title_needs_generation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_identity_context_id": { + "name": "origin_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "continuation_identity_context_id": { + "name": "continuation_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_binding_revision": { + "name": "workspace_binding_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "workspace_selection": { + "name": "workspace_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "workspace_pending_selection": { + "name": "workspace_pending_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_conversation_identity_idx": { + "name": "issues_conversation_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_parent_idx": { + "name": "issues_company_privacy_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_parent_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_root_idx": { + "name": "issues_company_privacy_root_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_conversation_agent_id_agents_id_fk": { + "name": "issues_conversation_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "conversation_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_root_issue_id_issues_id_fk": { + "name": "issues_privacy_root_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_parent_issue_id_issues_id_fk": { + "name": "issues_privacy_parent_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "issues_visibility_check": { + "name": "issues_visibility_check", + "value": "\"issues\".\"visibility\" in ('open', 'private')" + }, + "issues_conversation_identity_check": { + "name": "issues_conversation_identity_check", + "value": "(\n \"issues\".\"conversation_agent_id\" is null and \"issues\".\"conversation_user_id\" is null and \"issues\".\"conversation_state\" is null\n ) or (\n \"issues\".\"conversation_agent_id\" is not null and \"issues\".\"conversation_user_id\" is not null\n and \"issues\".\"assignee_agent_id\" = \"issues\".\"conversation_agent_id\" and \"issues\".\"assignee_agent_id\" is not null\n and \"issues\".\"assignee_user_id\" is null and \"issues\".\"conversation_state\" is not null\n and \"issues\".\"conversation_state\" in ('active', 'waiting')\n and \"issues\".\"status\" not in ('done', 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_pid": { + "name": "controller_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "controller_process_started_at": { + "name": "controller_process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recovery_state": { + "name": "recovery_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_request_id": { + "name": "recovery_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_history": { + "name": "recovery_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "control_deadline_at": { + "name": "control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_finalizations_control_deadline_idx": { + "name": "native_run_finalizations_control_deadline_idx", + "columns": [ + { + "expression": "control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"native_run_finalizations\".\"control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_lifecycle_acknowledgments": { + "name": "plugin_lifecycle_acknowledgments", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_lifecycle_acknowledgments_event_idx": { + "name": "plugin_lifecycle_acknowledgments_event_idx", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk": { + "name": "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "resource_lifecycle_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk", + "columns": [ + "plugin_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_access_members": { + "name": "project_access_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_access_members_project_subject_uq": { + "name": "project_access_members_project_subject_uq", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_access_members_subject_lookup_idx": { + "name": "project_access_members_subject_lookup_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_access_members_company_id_companies_id_fk": { + "name": "project_access_members_company_id_companies_id_fk", + "tableFrom": "project_access_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_access_members_project_id_projects_id_fk": { + "name": "project_access_members_project_id_projects_id_fk", + "tableFrom": "project_access_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "project_access_members_subject_type_check": { + "name": "project_access_members_subject_type_check", + "value": "\"project_access_members\".\"subject_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_owner_user_id": { + "name": "privacy_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "personal_owner_user_id": { + "name": "personal_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "projects_company_personal_owner_uq": { + "name": "projects_company_personal_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "personal_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"projects\".\"personal_owner_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "projects_visibility_check": { + "name": "projects_visibility_check", + "value": "\"projects\".\"visibility\" in ('open', 'private')" + } + }, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.resource_lifecycle_events": { + "name": "resource_lifecycle_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "resource_lifecycle_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_lifecycle_events_creation_idx": { + "name": "resource_lifecycle_events_creation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"resource_lifecycle_events\".\"action\" = 'create'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_company_idx": { + "name": "resource_lifecycle_events_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_resource_order_idx": { + "name": "resource_lifecycle_events_resource_order_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_lifecycle_events_company_id_companies_id_fk": { + "name": "resource_lifecycle_events_company_id_companies_id_fk", + "tableFrom": "resource_lifecycle_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_lifecycle_events_resource_type_check": { + "name": "resource_lifecycle_events_resource_type_check", + "value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')" + }, + "resource_lifecycle_events_action_check": { + "name": "resource_lifecycle_events_action_check", + "value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'project' AND \"resource_lifecycle_events\".\"action\" IN ('update', 'archive')) OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "setup_pending": { + "name": "setup_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_webhook_delivery": { + "name": "last_webhook_delivery", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_webhook_test_receipts": { + "name": "routine_webhook_test_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_key_hash": { + "name": "delivery_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_webhook_test_receipts_delivery_uq": { + "name": "routine_webhook_test_receipts_delivery_uq", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_webhook_test_receipts_company_id_companies_id_fk": { + "name": "routine_webhook_test_receipts_company_id_companies_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk": { + "name": "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.run_identity_contexts": { + "name": "run_identity_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_context_id": { + "name": "parent_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_identity_contexts_run_revision_idx": { + "name": "run_identity_contexts_run_revision_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_run_correlation_idx": { + "name": "run_identity_contexts_run_correlation_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_company_run_idx": { + "name": "run_identity_contexts_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_identity_contexts_company_id_companies_id_fk": { + "name": "run_identity_contexts_company_id_companies_id_fk", + "tableFrom": "run_identity_contexts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runner_api_response_reservations": { + "name": "runner_api_response_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reserved_bytes": { + "name": "reserved_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "runner_api_response_reservations_company_idx": { + "name": "runner_api_response_reservations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runner_api_response_reservations_company_id_companies_id_fk": { + "name": "runner_api_response_reservations_company_id_companies_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runner_api_response_reservations_run_id_heartbeat_runs_id_fk": { + "name": "runner_api_response_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "runner_api_response_reservations_asset_id_assets_id_fk": { + "name": "runner_api_response_reservations_asset_id_assets_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_deliveries": { + "name": "tool_action_deliveries", + "schema": "", + "columns": { + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_deliveries_pending_idx": { + "name": "tool_action_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_deliveries_action_request_id_tool_action_requests_id_fk": { + "name": "tool_action_deliveries_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_company_id_companies_id_fk": { + "name": "tool_action_deliveries_company_id_companies_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_issue_id_issues_id_fk": { + "name": "tool_action_deliveries_issue_id_issues_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_snapshots": { + "name": "tool_connection_app_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toolkit": { + "name": "toolkit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accounts": { + "name": "accounts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error_at": { + "name": "error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tool_connection_app_snapshots_owner_toolkit_uq": { + "name": "tool_connection_app_snapshots_owner_toolkit_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "toolkit", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_snapshots_company_id_companies_id_fk": { + "name": "tool_connection_app_snapshots_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_snapshots_company_connection_fk": { + "name": "tool_connection_app_snapshots_company_connection_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_syncs": { + "name": "tool_connection_app_syncs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checked": { + "name": "checked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total": { + "name": "total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_app_syncs_owner_key_uq": { + "name": "tool_connection_app_syncs_owner_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_syncs_company_id_companies_id_fk": { + "name": "tool_connection_app_syncs_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_syncs_company_connection_fk": { + "name": "tool_connection_app_syncs_company_connection_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "connection_purpose": { + "name": "connection_purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_instructions": { + "name": "agent_instructions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'voice', 'runtime_auth')" + }, + "tool_connections_purpose_check": { + "name": "tool_connections_purpose_check", + "value": "\"tool_connections\".\"connection_purpose\" in ('tool', 'channel', 'ai')" + }, + "tool_connections_channel_transport_check": { + "name": "tool_connections_channel_transport_check", + "value": "(\n (\"tool_connections\".\"connection_purpose\" = 'tool' and \"tool_connections\".\"transport\" not in ('chat_sdk', 'voice', 'runtime_auth'))\n or\n (\"tool_connections\".\"connection_purpose\" = 'channel' and (\"tool_connections\".\"transport\" in ('chat_sdk', 'voice') or (\"tool_connections\".\"transport\" = 'rest_api' and \"tool_connections\".\"config\"->>'provider' = 'agentmail')))\n or\n (\"tool_connections\".\"connection_purpose\" = 'ai' and \"tool_connections\".\"transport\" = 'runtime_auth')\n )" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback', 'per_agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_expiry_idx": { + "name": "tool_mcp_gateway_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_subject_agent_idx": { + "name": "tool_oauth_states_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_subject_agent_id_agents_id_fk": { + "name": "tool_oauth_states_subject_agent_id_agents_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_company_preferences": { + "name": "user_company_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_agent_id": { + "name": "primary_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "primary_agent_initialized": { + "name": "primary_agent_initialized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_company_preferences_company_user_uq": { + "name": "user_company_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_company_preferences_primary_agent_idx": { + "name": "user_company_preferences_primary_agent_idx", + "columns": [ + { + "expression": "primary_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_company_preferences_company_id_companies_id_fk": { + "name": "user_company_preferences_company_id_companies_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_company_preferences_primary_agent_id_agents_id_fk": { + "name": "user_company_preferences_primary_agent_id_agents_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "agents", + "columnsFrom": [ + "primary_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_attachment_uploads": { + "name": "mcp_attachment_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cleaned_at": { + "name": "cleaned_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": { + "mcp_attachment_upload_request_uq": { + "name": "mcp_attachment_upload_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_attachment_upload_expiry_idx": { + "name": "mcp_attachment_upload_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_attachment_uploads_company_id_companies_id_fk": { + "name": "mcp_attachment_uploads_company_id_companies_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_attachment_uploads_user_id_user_id_fk": { + "name": "mcp_attachment_uploads_user_id_user_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_admissions": { + "name": "mcp_event_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reserves_subscription": { + "name": "reserves_subscription", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "mcp_event_admissions_expiry_idx": { + "name": "mcp_event_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_grant_idx": { + "name": "mcp_event_admissions_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_company_idx": { + "name": "mcp_event_admissions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_admissions_company_id_companies_id_fk": { + "name": "mcp_event_admissions_company_id_companies_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_deliveries": { + "name": "mcp_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mailbox_item_id": { + "name": "mailbox_item_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_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 + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "mcp_event_deliveries_mailbox_uq": { + "name": "mcp_event_deliveries_mailbox_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mailbox_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_activity_uq": { + "name": "mcp_event_deliveries_activity_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_due_idx": { + "name": "mcp_event_deliveries_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk": { + "name": "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk", + "tableFrom": "mcp_event_deliveries", + "tableTo": "mcp_event_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_delivery_source_check": { + "name": "mcp_delivery_source_check", + "value": "(\"mcp_event_deliveries\".\"activity_id\" IS NOT NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NULL) OR (\"mcp_event_deliveries\".\"activity_id\" IS NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_event_subscriptions": { + "name": "mcp_event_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_material": { + "name": "delivery_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_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 + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scanned_at": { + "name": "scanned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_event_subscriptions_expiry_idx": { + "name": "mcp_event_subscriptions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_subscriptions_company_idx": { + "name": "mcp_event_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_subscriptions_company_id_companies_id_fk": { + "name": "mcp_event_subscriptions_company_id_companies_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_subscription_resource_check": { + "name": "mcp_subscription_resource_check", + "value": "(\"mcp_event_subscriptions\".\"task_id\" IS NOT NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NULL) OR (\"mcp_event_subscriptions\".\"task_id\" IS NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_file_tickets": { + "name": "mcp_file_tickets", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "upload_id": { + "name": "upload_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_file_ticket_expiry_idx": { + "name": "mcp_file_ticket_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_file_ticket_grant_idx": { + "name": "mcp_file_ticket_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_file_tickets_company_id_companies_id_fk": { + "name": "mcp_file_tickets_company_id_companies_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk": { + "name": "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_attachment_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_mutation_receipts": { + "name": "mcp_mutation_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "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()" + } + }, + "indexes": { + "mcp_mutation_receipts_request_uq": { + "name": "mcp_mutation_receipts_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_mutation_receipts_company_idx": { + "name": "mcp_mutation_receipts_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_mutation_receipts_company_id_companies_id_fk": { + "name": "mcp_mutation_receipts_company_id_companies_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_user_id_user_id_fk": { + "name": "mcp_mutation_receipts_user_id_user_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_clients": { + "name": "mcp_oauth_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registration_source_hash": { + "name": "registration_source_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "grant_types": { + "name": "grant_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"authorization_code\",\"refresh_token\"]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_device_requests": { + "name": "mcp_oauth_device_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code_hash": { + "name": "user_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "next_poll_at": { + "name": "next_poll_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_device_code_uq": { + "name": "mcp_oauth_device_code_uq", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_user_code_uq": { + "name": "mcp_oauth_user_code_uq", + "columns": [ + { + "expression": "user_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_device_expiry_idx": { + "name": "mcp_oauth_device_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_grants": { + "name": "mcp_oauth_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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()" + } + }, + "indexes": { + "mcp_oauth_grants_user_company_idx": { + "name": "mcp_oauth_grants_user_company_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_grants_company_id_companies_id_fk": { + "name": "mcp_oauth_grants_company_id_companies_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_user_id_user_id_fk": { + "name": "mcp_oauth_grants_user_id_user_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_agent_id_agents_id_fk": { + "name": "mcp_oauth_grants_agent_id_agents_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_metadata_admissions": { + "name": "mcp_oauth_metadata_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_oauth_metadata_admissions_expiry_idx": { + "name": "mcp_oauth_metadata_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_requests": { + "name": "mcp_oauth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_requests_code_uq": { + "name": "mcp_oauth_requests_code_uq", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_requests_expiry_idx": { + "name": "mcp_oauth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_tokens": { + "name": "mcp_oauth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_tokens_hash_uq": { + "name": "mcp_oauth_tokens_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_grant_idx": { + "name": "mcp_oauth_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_expiry_idx": { + "name": "mcp_oauth_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_tokens", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": { + "public.chat_telegram_draft_ids": { + "name": "chat_telegram_draft_ids", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0338_snapshot.json b/packages/db/src/migrations/meta/0338_snapshot.json new file mode 100644 index 00000000000..cc521642c51 --- /dev/null +++ b/packages/db/src/migrations/meta/0338_snapshot.json @@ -0,0 +1,59587 @@ +{ + "id": "108e07e7-19ab-4f16-8400-fd733f77afdc", + "prevId": "65f48b10-7f94-449e-b2ce-a68f220910f3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounting_runtime_baselines": { + "name": "accounting_runtime_baselines", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "numeric(30, 0)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounting_runtime_baselines_company_idx": { + "name": "accounting_runtime_baselines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounting_runtime_baselines_agent_id_agents_id_fk": { + "name": "accounting_runtime_baselines_agent_id_agents_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "accounting_runtime_baselines_company_id_companies_id_fk": { + "name": "accounting_runtime_baselines_company_id_companies_id_fk", + "tableFrom": "accounting_runtime_baselines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.billing_invoice_lines": { + "name": "billing_invoice_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_id": { + "name": "invoice_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "billing_invoice_lines_identity_idx": { + "name": "billing_invoice_lines_identity_idx", + "columns": [ + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "billing_invoice_lines_company_idx": { + "name": "billing_invoice_lines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoice_lines_company_id_companies_id_fk": { + "name": "billing_invoice_lines_company_id_companies_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_invoice_id_billing_invoices_id_fk": { + "name": "billing_invoice_lines_invoice_id_billing_invoices_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "billing_invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "billing_invoice_lines_cost_event_id_cost_events_id_fk": { + "name": "billing_invoice_lines_cost_event_id_cost_events_id_fk", + "tableFrom": "billing_invoice_lines", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "billing_invoice_lines_amount_check": { + "name": "billing_invoice_lines_amount_check", + "value": "\"billing_invoice_lines\".\"amount_cents\" >= 0" + }, + "billing_invoice_lines_kind_check": { + "name": "billing_invoice_lines_kind_check", + "value": "\"billing_invoice_lines\".\"kind\" in ('inference', 'fee', 'credit')" + } + }, + "isRLSEnabled": false + }, + "public.billing_invoices": { + "name": "billing_invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imported_by": { + "name": "imported_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "billing_invoices_identity_idx": { + "name": "billing_invoices_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "billing_invoices_company_id_companies_id_fk": { + "name": "billing_invoices_company_id_companies_id_fk", + "tableFrom": "billing_invoices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_reservations": { + "name": "budget_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_invocation_id": { + "name": "decision_invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'held'" + }, + "provider_started_at": { + "name": "provider_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_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": { + "budget_reservations_fast_response_idx": { + "name": "budget_reservations_fast_response_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fast_response_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_decision_idx": { + "name": "budget_reservations_decision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_run_idx": { + "name": "budget_reservations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_reservations_active_idx": { + "name": "budget_reservations_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"budget_reservations\".\"state\" = 'held'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_reservations_company_id_companies_id_fk": { + "name": "budget_reservations_company_id_companies_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_run_id_heartbeat_runs_id_fk": { + "name": "budget_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "budget_reservations_agent_id_agents_id_fk": { + "name": "budget_reservations_agent_id_agents_id_fk", + "tableFrom": "budget_reservations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_reservations_source_check": { + "name": "budget_reservations_source_check", + "value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int + (\"budget_reservations\".\"fast_response_request_id\" is not null)::int = 1" + }, + "budget_reservations_state_check": { + "name": "budget_reservations_state_check", + "value": "\"budget_reservations\".\"state\" in ('held', 'settled', 'released')" + }, + "budget_reservations_amount_check": { + "name": "budget_reservations_amount_check", + "value": "\"budget_reservations\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.cost_adjustments": { + "name": "cost_adjustments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invoice_line_id": { + "name": "invoice_line_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_cents": { + "name": "previous_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "corrected_cents": { + "name": "corrected_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "previous_status": { + "name": "previous_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_pricing": { + "name": "previous_pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing": { + "name": "pricing", + "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": { + "cost_adjustments_receipt_idx": { + "name": "cost_adjustments_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_event_idx": { + "name": "cost_adjustments_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_adjustments_invoice_line_idx": { + "name": "cost_adjustments_invoice_line_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_line_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_adjustments_company_id_companies_id_fk": { + "name": "cost_adjustments_company_id_companies_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_cost_event_id_cost_events_id_fk": { + "name": "cost_adjustments_cost_event_id_cost_events_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk": { + "name": "cost_adjustments_invoice_line_id_billing_invoice_lines_id_fk", + "tableFrom": "cost_adjustments", + "tableTo": "billing_invoice_lines", + "columnsFrom": [ + "invoice_line_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_adjustments_amount_check": { + "name": "cost_adjustments_amount_check", + "value": "\"cost_adjustments\".\"previous_cents\" >= 0 and \"cost_adjustments\".\"corrected_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.provider_billing_snapshots": { + "name": "provider_billing_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_billing_snapshots_identity_idx": { + "name": "provider_billing_snapshots_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_billing_snapshots_company_id_companies_id_fk": { + "name": "provider_billing_snapshots_company_id_companies_id_fk", + "tableFrom": "provider_billing_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "provider_billing_snapshots_amount_check": { + "name": "provider_billing_snapshots_amount_check", + "value": "\"provider_billing_snapshots\".\"amount_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.run_usage_receipts": { + "name": "run_usage_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt_json": { + "name": "receipt_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_usage_receipts_source_sequence_idx": { + "name": "run_usage_receipts_source_sequence_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_usage_receipts_run_latest_idx": { + "name": "run_usage_receipts_run_latest_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_usage_receipts_company_id_companies_id_fk": { + "name": "run_usage_receipts_company_id_companies_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "run_usage_receipts_run_id_heartbeat_runs_id_fk": { + "name": "run_usage_receipts_run_id_heartbeat_runs_id_fk", + "tableFrom": "run_usage_receipts", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "run_usage_receipts_positive_sequence": { + "name": "run_usage_receipts_positive_sequence", + "value": "\"run_usage_receipts\".\"sequence\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ai_connection": { + "name": "ai_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_grant_id": { + "name": "connection_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_method": { + "name": "connection_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_claim": { + "name": "result_claim", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commentary": { + "name": "agent_commentary", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_commentary_run_key_uq": { + "name": "agent_commentary_run_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commentary_company_kind_created_idx": { + "name": "agent_commentary_company_kind_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commentary_company_id_companies_id_fk": { + "name": "agent_commentary_company_id_companies_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_agent_id_agents_id_fk": { + "name": "agent_commentary_agent_id_agents_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_run_id_heartbeat_runs_id_fk": { + "name": "agent_commentary_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commentary_issue_id_issues_id_fk": { + "name": "agent_commentary_issue_id_issues_id_fk", + "tableFrom": "agent_commentary", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_commentary_kind_check": { + "name": "agent_commentary_kind_check", + "value": "\"agent_commentary\".\"kind\" in ('complaint', 'suggestion')" + } + }, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_identity_keys": { + "name": "agent_identity_keys", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key_pem": { + "name": "public_key_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_material": { + "name": "private_key_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_identity_keys_owner_fk": { + "name": "agent_identity_keys_owner_fk", + "tableFrom": "agent_identity_keys", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_identity_keys_algorithm_check": { + "name": "agent_identity_keys_algorithm_check", + "value": "\"agent_identity_keys\".\"algorithm\" = 'Ed25519'" + } + }, + "isRLSEnabled": false + }, + "public.agent_instruction_heads": { + "name": "agent_instruction_heads", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision_id": { + "name": "revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk": { + "name": "agent_instruction_heads_company_id_agent_id_entry_file_revision_id_agent_instruction_revisions_company_id_agent_id_entry_file_id_fk", + "tableFrom": "agent_instruction_heads", + "tableTo": "agent_instruction_revisions", + "columnsFrom": [ + "company_id", + "agent_id", + "entry_file", + "revision_id" + ], + "columnsTo": [ + "company_id", + "agent_id", + "entry_file", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_heads_identity_uq": { + "name": "agent_instruction_heads_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_revisions": { + "name": "agent_instruction_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_base64": { + "name": "content_base64", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_length": { + "name": "byte_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parent_revision_id": { + "name": "parent_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_revisions_history_idx": { + "name": "agent_instruction_revisions_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entry_file", + "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": { + "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_revisions_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_revisions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agent_instruction_revisions_identity_uq": { + "name": "agent_instruction_revisions_identity_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "agent_id", + "entry_file", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_instruction_working_copies": { + "name": "agent_instruction_working_copies", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entry_file": { + "name": "entry_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_hash": { + "name": "base_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_root": { + "name": "local_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_root": { + "name": "execution_root", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prepared'" + }, + "candidate_base64": { + "name": "candidate_base64", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "candidate_hash": { + "name": "candidate_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "process_stopped_at": { + "name": "process_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_instruction_copies_pending_idx": { + "name": "agent_instruction_copies_pending_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_instruction_copies_agent_idx": { + "name": "agent_instruction_copies_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk": { + "name": "agent_instruction_working_copies_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk": { + "name": "agent_instruction_working_copies_company_id_agent_id_agents_company_id_id_fk", + "tableFrom": "agent_instruction_working_copies", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_session_goal_actions": { + "name": "agent_session_goal_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_goal_actions_session_request_uniq": { + "name": "agent_session_goal_actions_session_request_uniq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_company_status_created_idx": { + "name": "agent_session_goal_actions_company_status_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_session_created_idx": { + "name": "agent_session_goal_actions_session_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_session_goal_actions_company_id_companies_id_fk": { + "name": "agent_session_goal_actions_company_id_companies_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_session_goal_actions_session_id_agent_task_sessions_id_fk": { + "name": "agent_session_goal_actions_session_id_agent_task_sessions_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "agent_task_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_capability_json": { + "name": "goal_capability_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_json": { + "name": "goal_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_status": { + "name": "goal_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_desired_state": { + "name": "goal_desired_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_id": { + "name": "goal_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_cursor": { + "name": "goal_source_cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "goal_revision": { + "name": "goal_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goal_observed_at": { + "name": "goal_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' OR \"agent_wakeup_requests\".\"idempotency_key\" LIKE 'interaction:%') AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_history_idx": { + "name": "agent_wakeup_requests_question_response_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'connection-intent:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_tool_action_delivery_uq": { + "name": "agent_wakeup_requests_tool_action_delivery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'tool-action-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_chat_completion_uq": { + "name": "agent_wakeup_requests_chat_completion_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'chat-completion:%'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appearance": { + "name": "appearance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "lifecycle_required_plugin_ids": { + "name": "lifecycle_required_plugin_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lifecycle_holds": { + "name": "lifecycle_holds", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "lifecycle_version": { + "name": "lifecycle_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lifecycle_error": { + "name": "lifecycle_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_operation": { + "name": "lifecycle_operation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_lifecycle_work_idx": { + "name": "agents_lifecycle_work_idx", + "columns": [ + { + "expression": "lifecycle_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_company_id_uq": { + "name": "agents_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_defaults": { + "name": "ai_connection_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_defaults_owner_method_uq": { + "name": "ai_connection_defaults_owner_method_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_defaults_company_id_companies_id_fk": { + "name": "ai_connection_defaults_company_id_companies_id_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_defaults_company_grant_fk": { + "name": "ai_connection_defaults_company_grant_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_connection_defaults_provider_check": { + "name": "ai_connection_defaults_provider_check", + "value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + }, + "ai_connection_defaults_method_check": { + "name": "ai_connection_defaults_method_check", + "value": "\"ai_connection_defaults\".\"method\" in ('subscription','api_key')" + } + }, + "isRLSEnabled": false + }, + "public.ai_connection_pools": { + "name": "ai_connection_pools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_pools_id_tool_connections_id_fk": { + "name": "ai_connection_pools_id_tool_connections_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_id_companies_id_fk": { + "name": "ai_connection_pools_company_id_companies_id_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_pools_company_connection_fk": { + "name": "ai_connection_pools_company_connection_fk", + "tableFrom": "ai_connection_pools", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_connection_pools_company_id_uq": { + "name": "ai_connection_pools_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_router_cursors": { + "name": "ai_connection_router_cursors", + "schema": "", + "columns": { + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "last_member_id": { + "name": "last_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_router_cursors_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_id_companies_id_fk": { + "name": "ai_connection_router_cursors_company_id_companies_id_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_router_cursors_company_pool_fk": { + "name": "ai_connection_router_cursors_company_pool_fk", + "tableFrom": "ai_connection_router_cursors", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_task_pins": { + "name": "ai_connection_task_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pool_id": { + "name": "pool_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "selection": { + "name": "selection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "member": { + "name": "member", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_task_pins_affinity_uq": { + "name": "ai_connection_task_pins_affinity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pool_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_task_pins_company_id_companies_id_fk": { + "name": "ai_connection_task_pins_company_id_companies_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk": { + "name": "ai_connection_task_pins_pool_id_ai_connection_pools_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "pool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_agent_id_agents_id_fk": { + "name": "ai_connection_task_pins_agent_id_agents_id_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_pool_fk": { + "name": "ai_connection_task_pins_company_pool_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "ai_connection_pools", + "columnsFrom": [ + "company_id", + "pool_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_task_pins_company_agent_fk": { + "name": "ai_connection_task_pins_company_agent_fk", + "tableFrom": "ai_connection_task_pins", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_provider_defaults": { + "name": "ai_provider_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_provider_defaults_owner_provider_uq": { + "name": "ai_provider_defaults_owner_provider_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_provider_defaults_company_id_companies_id_fk": { + "name": "ai_provider_defaults_company_id_companies_id_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_provider_defaults_company_grant_fk": { + "name": "ai_provider_defaults_company_grant_fk", + "tableFrom": "ai_provider_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_provider_defaults_provider_check": { + "name": "ai_provider_defaults_provider_check", + "value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai','google')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscription_connections": { + "name": "ai_subscription_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ai_subscription_connections_grant_idx": { + "name": "ai_subscription_connections_grant_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_credential_idx": { + "name": "ai_subscription_connections_credential_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_connections_account_idx": { + "name": "ai_subscription_connections_account_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_connections_company_id_companies_id_fk": { + "name": "ai_subscription_connections_company_id_companies_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_connection_id_tool_connections_id_fk": { + "name": "ai_subscription_connections_connection_id_tool_connections_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_grant_id_connection_grants_id_fk": { + "name": "ai_subscription_connections_grant_id_connection_grants_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_connections_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_connections", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_subscription_prices": { + "name": "ai_subscription_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'month'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscription_prices_revision_idx": { + "name": "ai_subscription_prices_revision_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscription_prices_company_idx": { + "name": "ai_subscription_prices_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscription_prices_company_id_companies_id_fk": { + "name": "ai_subscription_prices_company_id_companies_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk": { + "name": "ai_subscription_prices_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscription_prices", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscription_prices_valid": { + "name": "ai_subscription_prices_valid", + "value": "(\"ai_subscription_prices\".\"amount_cents\" is null or \"ai_subscription_prices\".\"amount_cents\" >= 0) and \"ai_subscription_prices\".\"cadence\" in ('month','year') and \"ai_subscription_prices\".\"status\" in ('active','ended','excluded') and \"ai_subscription_prices\".\"source\" in ('catalog','user','unknown')" + } + }, + "isRLSEnabled": false + }, + "public.ai_subscriptions": { + "name": "ai_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_key": { + "name": "account_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity_verified": { + "name": "identity_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_ids": { + "name": "owner_user_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "detected_plan": { + "name": "detected_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_checked_at": { + "name": "last_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_status": { + "name": "refresh_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_into_id": { + "name": "merged_into_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_subscriptions_identity_idx": { + "name": "ai_subscriptions_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_subscriptions_company_idx": { + "name": "ai_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_subscriptions_company_id_companies_id_fk": { + "name": "ai_subscriptions_company_id_companies_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk": { + "name": "ai_subscriptions_merged_into_id_ai_subscriptions_id_fk", + "tableFrom": "ai_subscriptions", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "merged_into_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_subscriptions_no_self_merge": { + "name": "ai_subscriptions_no_self_merge", + "value": "\"ai_subscriptions\".\"merged_into_id\" is null or \"ai_subscriptions\".\"merged_into_id\" <> \"ai_subscriptions\".\"id\"" + } + }, + "isRLSEnabled": false + }, + "public.announcement_dismissals": { + "name": "announcement_dismissals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "announcement_dismissals_user_id_announcement_id_pk": { + "name": "announcement_dismissals_user_id_announcement_id_pk", + "columns": [ + "user_id", + "announcement_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.announcement_publications": { + "name": "announcement_publications", + "schema": "", + "columns": { + "announcement_id": { + "name": "announcement_id", + "type": "text", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_browsers": { + "name": "browser_use_browsers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_browser_id": { + "name": "provider_browser_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "browser_use_browsers_provider_uq": { + "name": "browser_use_browsers_provider_uq", + "columns": [ + { + "expression": "provider_browser_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_browsers_session_idx": { + "name": "browser_use_browsers_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_browsers_company_id_companies_id_fk": { + "name": "browser_use_browsers_company_id_companies_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_browsers_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_browsers_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_browsers", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_runs": { + "name": "browser_use_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_run_id": { + "name": "provider_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "recovery_cursor": { + "name": "recovery_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'creating'" + }, + "detached_until": { + "name": "detached_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_cursor": { + "name": "event_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "events_drained": { + "name": "events_drained", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "accounted_cents": { + "name": "accounted_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress": { + "name": "progress", + "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()" + } + }, + "indexes": { + "browser_use_runs_invocation_uq": { + "name": "browser_use_runs_invocation_uq", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_provider_uq": { + "name": "browser_use_runs_provider_uq", + "columns": [ + { + "expression": "provider_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_runs_session_idx": { + "name": "browser_use_runs_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_runs_company_id_companies_id_fk": { + "name": "browser_use_runs_company_id_companies_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_session_id_browser_use_sessions_id_fk": { + "name": "browser_use_runs_session_id_browser_use_sessions_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "browser_use_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "browser_use_runs_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "browser_use_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_sessions": { + "name": "browser_use_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "idle_deadline": { + "name": "idle_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_requested": { + "name": "stop_requested", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_poll_at": { + "name": "next_poll_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": { + "browser_use_sessions_task_idx": { + "name": "browser_use_sessions_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "browser_use_sessions_provider_uq": { + "name": "browser_use_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "browser_use_sessions_company_id_companies_id_fk": { + "name": "browser_use_sessions_company_id_companies_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_issue_id_issues_id_fk": { + "name": "browser_use_sessions_issue_id_issues_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_agent_id_agents_id_fk": { + "name": "browser_use_sessions_agent_id_agents_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_connection_id_tool_connections_id_fk": { + "name": "browser_use_sessions_connection_id_tool_connections_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_sessions_grant_id_connection_grants_id_fk": { + "name": "browser_use_sessions_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_sessions", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.browser_use_settings": { + "name": "browser_use_settings", + "schema": "", + "columns": { + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allowed_profile_ids": { + "name": "allowed_profile_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "max_cost_usd": { + "name": "max_cost_usd", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "browser_use_settings_grant_id_connection_grants_id_fk": { + "name": "browser_use_settings_grant_id_connection_grants_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "connection_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "browser_use_settings_company_id_companies_id_fk": { + "name": "browser_use_settings_company_id_companies_id_fk", + "tableFrom": "browser_use_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" = 'open'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reservation_cents": { + "name": "reservation_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "unpriced_usage_policy": { + "name": "unpriced_usage_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'block'" + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enforcement_version": { + "name": "enforcement_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enforcement_delivered_version": { + "name": "enforcement_delivered_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "budget_policies_reservation_nonnegative": { + "name": "budget_policies_reservation_nonnegative", + "value": "\"budget_policies\".\"reservation_cents\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.chat_actions": { + "name": "chat_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_action_id": { + "name": "provider_action_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_actions_provider_action_uq": { + "name": "chat_actions_provider_action_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_actions_company_id_companies_id_fk": { + "name": "chat_actions_company_id_companies_id_fk", + "tableFrom": "chat_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_actions_delivery_id_chat_deliveries_id_fk": { + "name": "chat_actions_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_actions_company_delivery_fk": { + "name": "chat_actions_company_delivery_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_conversation_fk": { + "name": "chat_actions_company_conversation_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_principal_fk": { + "name": "chat_actions_company_principal_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_endpoint_fk": { + "name": "chat_actions_company_endpoint_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_agent_routes": { + "name": "chat_agent_routes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_endpoint_id": { + "name": "source_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "destination_endpoint_id": { + "name": "destination_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'explicit_mention'" + }, + "max_hops": { + "name": "max_hops", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_agent_routes_pair_uq": { + "name": "chat_agent_routes_pair_uq", + "columns": [ + { + "expression": "source_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "destination_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_agent_routes_company_id_companies_id_fk": { + "name": "chat_agent_routes_company_id_companies_id_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_source_fk": { + "name": "chat_agent_routes_company_source_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "source_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_destination_fk": { + "name": "chat_agent_routes_company_destination_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "destination_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_agent_routes_hops_check": { + "name": "chat_agent_routes_hops_check", + "value": "\"chat_agent_routes\".\"max_hops\" between 1 and 8" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_conversation_id": { + "name": "external_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_thread_id": { + "name": "external_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "external_label": { + "name": "external_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_direct_message": { + "name": "is_direct_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "communication_guidance": { + "name": "communication_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_conversations_issue_idx": { + "name": "chat_conversations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_conversations_thread_uq": { + "name": "chat_conversations_thread_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversations_company_id_companies_id_fk": { + "name": "chat_conversations_company_id_companies_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_resource_id_chat_endpoint_resources_id_fk": { + "name": "chat_conversations_resource_id_chat_endpoint_resources_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_conversations_issue_id_issues_id_fk": { + "name": "chat_conversations_issue_id_issues_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_conversations_company_issue_fk": { + "name": "chat_conversations_company_issue_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversations_company_endpoint_fk": { + "name": "chat_conversations_company_endpoint_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_company_resource_fk": { + "name": "chat_conversations_company_resource_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "company_id", + "resource_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversations_company_id_uq": { + "name": "chat_conversations_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_conversations_state_check": { + "name": "chat_conversations_state_check", + "value": "\"chat_conversations\".\"state\" in ('active', 'waiting', 'completed', 'unavailable', 'endpoint_removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_deliveries": { + "name": "chat_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_event": { + "name": "normalized_event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_deliveries_work_idx": { + "name": "chat_deliveries_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_event_uq": { + "name": "chat_deliveries_event_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_dedupe_uq": { + "name": "chat_deliveries_dedupe_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_deliveries_company_id_companies_id_fk": { + "name": "chat_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_conversation_id_chat_conversations_id_fk": { + "name": "chat_deliveries_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_principal_id_chat_external_principals_id_fk": { + "name": "chat_deliveries_principal_id_chat_external_principals_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "principal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_company_endpoint_fk": { + "name": "chat_deliveries_company_endpoint_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_company_conversation_fk": { + "name": "chat_deliveries_company_conversation_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_deliveries_company_principal_fk": { + "name": "chat_deliveries_company_principal_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_deliveries_company_id_uq": { + "name": "chat_deliveries_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_deliveries_state_check": { + "name": "chat_deliveries_state_check", + "value": "\"chat_deliveries\".\"state\" in ('received', 'filtered', 'processing', 'processed', 'retry', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoint_leases": { + "name": "chat_endpoint_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_leases_active_uq": { + "name": "chat_endpoint_leases_active_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_leases_expiry_idx": { + "name": "chat_endpoint_leases_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_leases_company_id_companies_id_fk": { + "name": "chat_endpoint_leases_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_leases_company_endpoint_fk": { + "name": "chat_endpoint_leases_company_endpoint_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_endpoint_resources": { + "name": "chat_endpoint_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_provider_resource_id": { + "name": "parent_provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "availability": { + "name": "availability", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_resources_endpoint_idx": { + "name": "chat_endpoint_resources_endpoint_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_resources_external_uq": { + "name": "chat_endpoint_resources_external_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_resources_company_id_companies_id_fk": { + "name": "chat_endpoint_resources_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_resources_company_endpoint_fk": { + "name": "chat_endpoint_resources_company_endpoint_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoint_resources_company_id_uq": { + "name": "chat_endpoint_resources_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoint_resources_availability_check": { + "name": "chat_endpoint_resources_availability_check", + "value": "\"chat_endpoint_resources\".\"availability\" in ('available', 'unavailable', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoints": { + "name": "chat_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publication_mode": { + "name": "publication_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'automatic'" + }, + "external_execution_policy": { + "name": "external_execution_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'restricted'" + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_user_id": { + "name": "sponsor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_account_label": { + "name": "provider_account_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_external_id": { + "name": "bot_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_username": { + "name": "bot_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_display_name": { + "name": "bot_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_avatar_url": { + "name": "bot_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "communication_instructions": { + "name": "communication_instructions", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "execution_defaults": { + "name": "execution_defaults", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "require_at_mention": { + "name": "require_at_mention", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_group_chats": { + "name": "allow_group_chats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_unlinked_people": { + "name": "allow_unlinked_people", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"threads\":false,\"directMessages\":false,\"nativeStreaming\":false,\"messageEdits\":false,\"messageDeletes\":false,\"reactions\":false,\"files\":false,\"cards\":false,\"actions\":false,\"modals\":false,\"slashCommands\":false,\"ephemeralMessages\":false,\"proactiveDirectMessages\":false}'::jsonb" + }, + "setup": { + "name": "setup", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"step\":\"provider_setup\"}'::jsonb" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_publication_at": { + "name": "last_publication_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoints_company_idx": { + "name": "chat_endpoints_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agent_idx": { + "name": "chat_endpoints_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_status_idx": { + "name": "chat_endpoints_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_public_id_uq": { + "name": "chat_endpoints_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agentmail_inbox_uq": { + "name": "chat_endpoints_agentmail_inbox_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'agentmail' and \"chat_endpoints\".\"status\" != 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_connection_uq": { + "name": "chat_endpoints_connection_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_external_uq": { + "name": "chat_endpoints_live_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_discord_bot_external_uq": { + "name": "chat_endpoints_live_discord_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'discord'\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_global_app_bot_external_uq": { + "name": "chat_endpoints_live_global_app_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" in ('github', 'microsoft-teams')\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_username_uq": { + "name": "chat_endpoints_live_bot_username_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_username\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoints_company_id_companies_id_fk": { + "name": "chat_endpoints_company_id_companies_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoints_assigned_agent_id_agents_id_fk": { + "name": "chat_endpoints_assigned_agent_id_agents_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "assigned_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_endpoints_company_agent_fk": { + "name": "chat_endpoints_company_agent_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_endpoints_company_connection_fk": { + "name": "chat_endpoints_company_connection_fk", + "tableFrom": "chat_endpoints", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoints_company_id_uq": { + "name": "chat_endpoints_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoints_publication_mode_check": { + "name": "chat_endpoints_publication_mode_check", + "value": "\"chat_endpoints\".\"publication_mode\" in ('automatic', 'explicit')" + }, + "chat_endpoints_execution_policy_check": { + "name": "chat_endpoints_execution_policy_check", + "value": "\"chat_endpoints\".\"external_execution_policy\" in ('restricted', 'agent')" + }, + "chat_endpoints_email_policy_check": { + "name": "chat_endpoints_email_policy_check", + "value": "\"chat_endpoints\".\"provider\" <> 'agentmail' or (\"chat_endpoints\".\"publication_mode\" = 'explicit' and \"chat_endpoints\".\"external_execution_policy\" = 'agent')" + }, + "chat_endpoints_provider_check": { + "name": "chat_endpoints_provider_check", + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_endpoints_status_check": { + "name": "chat_endpoints_status_check", + "value": "\"chat_endpoints\".\"status\" in ('draft', 'verifying', 'active', 'paused', 'attention', 'revoked', 'archived')" + }, + "chat_endpoints_deployment_check": { + "name": "chat_endpoints_deployment_check", + "value": "\"chat_endpoints\".\"deployment_mode\" in ('direct', 'relay')" + }, + "chat_endpoints_concurrency_check": { + "name": "chat_endpoints_concurrency_check", + "value": "\"chat_endpoints\".\"concurrency_policy\" in ('burst', 'queue', 'debounce', 'drop', 'concurrent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_external_principals": { + "name": "chat_external_principals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_external_principals_company_idx": { + "name": "chat_external_principals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_external_principals_external_uq": { + "name": "chat_external_principals_external_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_external_principals_company_id_companies_id_fk": { + "name": "chat_external_principals_company_id_companies_id_fk", + "tableFrom": "chat_external_principals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_external_principals_company_id_uq": { + "name": "chat_external_principals_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_external_principals_provider_check": { + "name": "chat_external_principals_provider_check", + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'speko', 'agentmail', 'imessage-photon')" + }, + "chat_external_principals_kind_check": { + "name": "chat_external_principals_kind_check", + "value": "\"chat_external_principals\".\"kind\" in ('user', 'bot', 'app', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.chat_identity_links": { + "name": "chat_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paperclip_user_id": { + "name": "paperclip_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "confirmation_token_hash": { + "name": "confirmation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_identity_links_user_idx": { + "name": "chat_identity_links_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paperclip_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_identity_links_endpoint_principal_uq": { + "name": "chat_identity_links_endpoint_principal_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_identity_links_company_id_companies_id_fk": { + "name": "chat_identity_links_company_id_companies_id_fk", + "tableFrom": "chat_identity_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_endpoint_fk": { + "name": "chat_identity_links_company_endpoint_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_principal_fk": { + "name": "chat_identity_links_company_principal_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_identity_links_status_check": { + "name": "chat_identity_links_status_check", + "value": "\"chat_identity_links\".\"status\" in ('pending', 'linked', 'revoked', 'expired')" + } + }, + "isRLSEnabled": false + }, + "public.chat_message_links": { + "name": "chat_message_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_message_links_provider_message_uq": { + "name": "chat_message_links_provider_message_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_links_company_id_companies_id_fk": { + "name": "chat_message_links_company_id_companies_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_delivery_id_chat_deliveries_id_fk": { + "name": "chat_message_links_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_publication_id_chat_publications_id_fk": { + "name": "chat_message_links_publication_id_chat_publications_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_comment_id_issue_comments_id_fk": { + "name": "chat_message_links_comment_id_issue_comments_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_company_endpoint_fk": { + "name": "chat_message_links_company_endpoint_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_company_delivery_fk": { + "name": "chat_message_links_company_delivery_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_publication_fk": { + "name": "chat_message_links_company_publication_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_comment_fk": { + "name": "chat_message_links_company_comment_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_conversation_fk": { + "name": "chat_message_links_company_conversation_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_message_links_direction_check": { + "name": "chat_message_links_direction_check", + "value": "\"chat_message_links\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.chat_publications": { + "name": "chat_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_publications_company_id_uq": { + "name": "chat_publications_company_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_work_idx": { + "name": "chat_publications_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_idempotency_uq": { + "name": "chat_publications_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_publications_company_id_companies_id_fk": { + "name": "chat_publications_company_id_companies_id_fk", + "tableFrom": "chat_publications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_issue_id_issues_id_fk": { + "name": "chat_publications_issue_id_issues_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_publications_comment_id_issue_comments_id_fk": { + "name": "chat_publications_comment_id_issue_comments_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_publications_company_issue_fk": { + "name": "chat_publications_company_issue_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_comment_fk": { + "name": "chat_publications_company_comment_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_endpoint_fk": { + "name": "chat_publications_company_endpoint_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_company_conversation_fk": { + "name": "chat_publications_company_conversation_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_publications_state_check": { + "name": "chat_publications_state_check", + "value": "\"chat_publications\".\"state\" in ('pending', 'streaming', 'published', 'retry', 'delivery_unknown', 'failed', 'cancelled', 'awaiting_consent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_sdk_state": { + "name": "chat_sdk_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sdk_state_key_uq": { + "name": "chat_sdk_state_key_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sdk_state_expiry_idx": { + "name": "chat_sdk_state_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sdk_state_company_id_companies_id_fk": { + "name": "chat_sdk_state_company_id_companies_id_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sdk_state_company_endpoint_fk": { + "name": "chat_sdk_state_company_endpoint_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_completion_deliveries": { + "name": "chat_completion_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status_version": { + "name": "status_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response_comment_id": { + "name": "response_comment_id", + "type": "uuid", + "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": { + "chat_completion_deliveries_transition_uq": { + "name": "chat_completion_deliveries_transition_uq", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_completion_deliveries_pending_idx": { + "name": "chat_completion_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_completion_deliveries_company_id_companies_id_fk": { + "name": "chat_completion_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk": { + "name": "chat_completion_deliveries_task_id_chat_task_handoffs_task_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "chat_task_handoffs", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "task_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "chat_completion_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_completion_deliveries_response_comment_id_issue_comments_id_fk": { + "name": "chat_completion_deliveries_response_comment_id_issue_comments_id_fk", + "tableFrom": "chat_completion_deliveries", + "tableTo": "issue_comments", + "columnsFrom": [ + "response_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_task_handoffs": { + "name": "chat_task_handoffs", + "schema": "", + "columns": { + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_task_handoffs_source_idx": { + "name": "chat_task_handoffs_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_task_handoffs_task_id_issues_id_fk": { + "name": "chat_task_handoffs_task_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_company_id_companies_id_fk": { + "name": "chat_task_handoffs_company_id_companies_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_conversation_id_issues_id_fk": { + "name": "chat_task_handoffs_conversation_id_issues_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "issues", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_task_handoffs_agent_id_agents_id_fk": { + "name": "chat_task_handoffs_agent_id_agents_id_fk", + "tableFrom": "chat_task_handoffs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_discord_command_owners": { + "name": "chat_discord_command_owners", + "schema": "", + "columns": { + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_id": { + "name": "action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_discord_command_owners_application_check": { + "name": "chat_discord_command_owners_application_check", + "value": "\"chat_discord_command_owners\".\"application_id\" ~ '^[1-9][0-9]{16,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_configurations": { + "name": "chat_github_configurations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_configurations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_configurations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_config_revision_check": { + "name": "chat_github_config_revision_check", + "value": "\"chat_github_configurations\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_registrations": { + "name": "chat_github_registrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trusted_origin": { + "name": "trusted_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "owner_login": { + "name": "owner_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handoff": { + "name": "handoff", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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": { + "chat_github_registration_state_uq": { + "name": "chat_github_registration_state_uq", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_registration_endpoint_idx": { + "name": "chat_github_registration_endpoint_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_github_registration_status_check": { + "name": "chat_github_registration_status_check", + "value": "\"chat_github_registrations\".\"status\" in ('pending', 'exchanging', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_github_reviews": { + "name": "chat_github_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pull_number": { + "name": "pull_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration_revision": { + "name": "configuration_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "policy_snapshot": { + "name": "policy_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "assessment": { + "name": "assessment", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "conclusion": { + "name": "conclusion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_url": { + "name": "check_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_id": { + "name": "summary_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_url": { + "name": "summary_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publication_receipts": { + "name": "publication_receipts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_github_reviews_delivery_uq": { + "name": "chat_github_reviews_delivery_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_pull_idx": { + "name": "chat_github_reviews_pull_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pull_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_github_reviews_task_idx": { + "name": "chat_github_reviews_task_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_github_reviews_run_id_heartbeat_runs_id_fk": { + "name": "chat_github_reviews_run_id_heartbeat_runs_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_github_reviews_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk": { + "name": "chat_github_reviews_company_id_issue_id_issues_company_id_id_fk", + "tableFrom": "chat_github_reviews", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_github_reviews_company_id_uq": { + "name": "chat_github_reviews_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_github_review_pull_number_check": { + "name": "chat_github_review_pull_number_check", + "value": "\"chat_github_reviews\".\"pull_number\" > 0" + }, + "chat_github_review_state_check": { + "name": "chat_github_review_state_check", + "value": "\"chat_github_reviews\".\"state\" in ('queued', 'running', 'completed', 'incomplete', 'error', 'superseded', 'manual_required')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_manager_grants": { + "name": "chat_slack_manager_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_name": { + "name": "workspace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_app_id": { + "name": "manager_app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "access_secret_id": { + "name": "access_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_secret_id": { + "name": "refresh_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rate_limits": { + "name": "rate_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_slack_manager_grants_company_id_idx": { + "name": "chat_slack_manager_grants_company_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_slack_manager_grants_owner_idx": { + "name": "chat_slack_manager_grants_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manager_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_slack_manager_grants_company_id_companies_id_fk": { + "name": "chat_slack_manager_grants_company_id_companies_id_fk", + "tableFrom": "chat_slack_manager_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_manager_grants_status_check": { + "name": "chat_slack_manager_grants_status_check", + "value": "\"chat_slack_manager_grants\".\"status\" in ('active', 'revoked', 'reauthorize')" + } + }, + "isRLSEnabled": false + }, + "public.chat_slack_registrations": { + "name": "chat_slack_registrations", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "manager_grant_id": { + "name": "manager_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "manifest_hash": { + "name": "manifest_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "callback_uri": { + "name": "callback_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_ids": { + "name": "secret_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_slack_manager_grants", + "columnsFrom": [ + "company_id", + "manager_grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_slack_registrations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_slack_registration_status_check": { + "name": "chat_slack_registration_status_check", + "value": "\"chat_slack_registrations\".\"status\" in ('creating', 'uncertain', 'failed', 'install', 'credentials_saved', 'configured', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_teams_file_transfers": { + "name": "chat_teams_file_transfers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "authorized_user_id": { + "name": "authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_generation": { + "name": "runtime_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_generation": { + "name": "conversation_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_digest": { + "name": "source_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authority_digest": { + "name": "authority_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aad_object_id": { + "name": "aad_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_sha256": { + "name": "token_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'consent_pending'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_expires_at": { + "name": "attempt_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_message_id": { + "name": "consent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_info_message_id": { + "name": "file_info_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_activity_id": { + "name": "response_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_digest": { + "name": "response_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_state": { + "name": "private_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_teams_file_transfers_publication_uq": { + "name": "chat_teams_file_transfers_publication_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_token_uq": { + "name": "chat_teams_file_transfers_token_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_work_idx": { + "name": "chat_teams_file_transfers_work_idx", + "columns": [ + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_teams_file_transfers_company_id_companies_id_fk": { + "name": "chat_teams_file_transfers_company_id_companies_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_issue_id_issues_id_fk": { + "name": "chat_teams_file_transfers_issue_id_issues_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_teams_file_transfers_phase_check": { + "name": "chat_teams_file_transfers_phase_check", + "value": "\"chat_teams_file_transfers\".\"phase\" in ('consent_pending','consent_sending','consent_unknown','awaiting_consent','upload_pending','uploading','upload_unknown','file_info_pending','file_info_sending','file_info_unknown','delivered','declined','expired','cancelled','conflict')" + }, + "chat_teams_file_transfers_bounds_check": { + "name": "chat_teams_file_transfers_bounds_check", + "value": "\"chat_teams_file_transfers\".\"version\" > 0 and \"chat_teams_file_transfers\".\"runtime_generation\" >= 0 and \"chat_teams_file_transfers\".\"conversation_generation\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" < 62914560" + }, + "chat_teams_file_transfers_hash_check": { + "name": "chat_teams_file_transfers_hash_check", + "value": "\"chat_teams_file_transfers\".\"source_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"authority_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"sha256\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"token_sha256\" ~ '^[a-f0-9]{64}$'" + }, + "chat_teams_file_transfers_attempt_check": { + "name": "chat_teams_file_transfers_attempt_check", + "value": "(\"chat_teams_file_transfers\".\"attempt_id\" is null) = (\"chat_teams_file_transfers\".\"attempt_expires_at\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_callbacks": { + "name": "chat_voice_callbacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_callbacks_owner_uq": { + "name": "chat_voice_callbacks_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_callbacks_endpoint_fk": { + "name": "chat_voice_callbacks_endpoint_fk", + "tableFrom": "chat_voice_callbacks", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_callbacks_phone_check": { + "name": "chat_voice_callbacks_phone_check", + "value": "\"chat_voice_callbacks\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_inbound_calls": { + "name": "chat_voice_inbound_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "approval_code": { + "name": "approval_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "intake_issue_id": { + "name": "intake_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_issue_id": { + "name": "requested_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_inbound_expiry_idx": { + "name": "chat_voice_inbound_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_inbound_endpoint_fk": { + "name": "chat_voice_inbound_endpoint_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_inbound_session_fk": { + "name": "chat_voice_inbound_session_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_intake_fk": { + "name": "chat_voice_inbound_intake_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "intake_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_inbound_requested_issue_fk": { + "name": "chat_voice_inbound_requested_issue_fk", + "tableFrom": "chat_voice_inbound_calls", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "requested_issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_inbound_provider_uq": { + "name": "chat_voice_inbound_provider_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_inbound_state_check": { + "name": "chat_voice_inbound_state_check", + "value": "\"chat_voice_inbound_calls\".\"state\" in ('guest_intake', 'awaiting_approval', 'approving', 'approved', 'denied', 'expired', 'ended')" + }, + "chat_voice_inbound_token_check": { + "name": "chat_voice_inbound_token_check", + "value": "\"chat_voice_inbound_calls\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_inbound_code_check": { + "name": "chat_voice_inbound_code_check", + "value": "\"chat_voice_inbound_calls\".\"approval_code\" ~ '^[0-9]{6}$'" + }, + "chat_voice_inbound_generation_check": { + "name": "chat_voice_inbound_generation_check", + "value": "\"chat_voice_inbound_calls\".\"generation\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_phone_lines": { + "name": "chat_voice_phone_lines", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_number_id": { + "name": "provider_number_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest_intake": { + "name": "guest_intake", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "low_trust_environment_id": { + "name": "low_trust_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk": { + "name": "chat_voice_phone_lines_low_trust_environment_id_environments_id_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "environments", + "columnsFrom": [ + "low_trust_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_phone_lines_endpoint_fk": { + "name": "chat_voice_phone_lines_endpoint_fk", + "tableFrom": "chat_voice_phone_lines", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_phone_lines_number_uq": { + "name": "chat_voice_phone_lines_number_uq", + "nullsNotDistinct": false, + "columns": [ + "provider_number_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_phone_lines_number_check": { + "name": "chat_voice_phone_lines_number_check", + "value": "\"chat_voice_phone_lines\".\"phone_number\" ~ '^\\+[1-9][0-9]{6,14}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_reports": { + "name": "chat_voice_reports", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "transcript": { + "name": "transcript", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cost_micro_usd": { + "name": "cost_micro_usd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_updated_at": { + "name": "provider_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_check_at": { + "name": "next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_reports_pending_idx": { + "name": "chat_voice_reports_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_reports_session_fk": { + "name": "chat_voice_reports_session_fk", + "tableFrom": "chat_voice_reports", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_reports_status_check": { + "name": "chat_voice_reports_status_check", + "value": "\"chat_voice_reports\".\"status\" in ('pending', 'available', 'unavailable')" + }, + "chat_voice_reports_cost_check": { + "name": "chat_voice_reports_cost_check", + "value": "\"chat_voice_reports\".\"cost_micro_usd\" is null or \"chat_voice_reports\".\"cost_micro_usd\" ~ '^[0-9]{1,30}$'" + }, + "chat_voice_reports_duration_check": { + "name": "chat_voice_reports_duration_check", + "value": "\"chat_voice_reports\".\"duration_seconds\" is null or \"chat_voice_reports\".\"duration_seconds\" >= 0" + }, + "chat_voice_reports_attempts_check": { + "name": "chat_voice_reports_attempts_check", + "value": "\"chat_voice_reports\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_replies": { + "name": "chat_voice_replies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "spoken_at": { + "name": "spoken_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": { + "chat_voice_replies_publication_uq": { + "name": "chat_voice_replies_publication_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_replies_cursor_uq": { + "name": "chat_voice_replies_cursor_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cursor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_replies_company_id_companies_id_fk": { + "name": "chat_voice_replies_company_id_companies_id_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_replies_session_fk": { + "name": "chat_voice_replies_session_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_replies_publication_fk": { + "name": "chat_voice_replies_publication_fk", + "tableFrom": "chat_voice_replies", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_replies_cursor_check": { + "name": "chat_voice_replies_cursor_check", + "value": "\"chat_voice_replies\".\"cursor\" > 0" + }, + "chat_voice_replies_spoken_check": { + "name": "chat_voice_replies_spoken_check", + "value": "\"chat_voice_replies\".\"spoken_at\" is null or \"chat_voice_replies\".\"delivered_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_sessions": { + "name": "chat_voice_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_id": { + "name": "caller_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "caller_authority": { + "name": "caller_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_session_id": { + "name": "provider_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_token_hash": { + "name": "tool_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reply_cursor": { + "name": "reply_cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_sessions_provider_uq": { + "name": "chat_voice_sessions_provider_uq", + "columns": [ + { + "expression": "provider_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_request_uq": { + "name": "chat_voice_sessions_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_live_caller_task_uq": { + "name": "chat_voice_sessions_live_caller_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_voice_sessions\".\"state\" not in ('ended', 'failed', 'expired')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_sessions_expiry_idx": { + "name": "chat_voice_sessions_expiry_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_sessions_company_id_companies_id_fk": { + "name": "chat_voice_sessions_company_id_companies_id_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_sessions_endpoint_fk": { + "name": "chat_voice_sessions_endpoint_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_conversation_fk": { + "name": "chat_voice_sessions_conversation_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_issue_fk": { + "name": "chat_voice_sessions_issue_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_sessions_agent_fk": { + "name": "chat_voice_sessions_agent_fk", + "tableFrom": "chat_voice_sessions", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_voice_sessions_company_id_uq": { + "name": "chat_voice_sessions_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_voice_sessions_state_check": { + "name": "chat_voice_sessions_state_check", + "value": "\"chat_voice_sessions\".\"state\" in ('reserved', 'creating', 'creation_unknown', 'connecting', 'active', 'awaiting_approval', 'ending', 'ended', 'failed', 'expired')" + }, + "chat_voice_sessions_mode_check": { + "name": "chat_voice_sessions_mode_check", + "value": "\"chat_voice_sessions\".\"mode\" in ('browser', 'inbound_phone', 'outbound_phone')" + }, + "chat_voice_sessions_authority_check": { + "name": "chat_voice_sessions_authority_check", + "value": "\"chat_voice_sessions\".\"caller_authority\" in ('member', 'instance_admin', 'local_board', 'guest_intake', 'pending_approval')" + }, + "chat_voice_sessions_counters_check": { + "name": "chat_voice_sessions_counters_check", + "value": "\"chat_voice_sessions\".\"generation\" >= 1 and \"chat_voice_sessions\".\"reply_cursor\" >= 0" + }, + "chat_voice_sessions_token_check": { + "name": "chat_voice_sessions_token_check", + "value": "\"chat_voice_sessions\".\"tool_token_hash\" ~ '^[a-f0-9]{64}$'" + }, + "chat_voice_sessions_expiry_check": { + "name": "chat_voice_sessions_expiry_check", + "value": "\"chat_voice_sessions\".\"expires_at\" > \"chat_voice_sessions\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.chat_voice_tool_calls": { + "name": "chat_voice_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_tool_call_id": { + "name": "provider_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_voice_tool_calls_tool_uq": { + "name": "chat_voice_tool_calls_tool_uq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_voice_tool_calls_webhook_uq": { + "name": "chat_voice_tool_calls_webhook_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_voice_tool_calls_company_id_companies_id_fk": { + "name": "chat_voice_tool_calls_company_id_companies_id_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_session_fk": { + "name": "chat_voice_tool_calls_session_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_voice_sessions", + "columnsFrom": [ + "company_id", + "session_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_voice_tool_calls_delivery_fk": { + "name": "chat_voice_tool_calls_delivery_fk", + "tableFrom": "chat_voice_tool_calls", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_voice_tool_calls_tool_check": { + "name": "chat_voice_tool_calls_tool_check", + "value": "\"chat_voice_tool_calls\".\"tool\" in ('submit_request', 'get_updates', 'answer_question')" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spend_month_utc": { + "name": "spend_month_utc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ai_session_epoch": { + "name": "ai_session_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_source_entries": { + "name": "company_skill_source_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inspection": { + "name": "inspection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "selection": { + "name": "selection", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "present": { + "name": "present", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_skill_source_entries_source_path_idx": { + "name": "company_skill_source_entries_source_path_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_source_entries_company_skill_idx": { + "name": "company_skill_source_entries_company_skill_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_source_entries_company_id_companies_id_fk": { + "name": "company_skill_source_entries_company_id_companies_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_source_id_company_skill_sources_id_fk": { + "name": "company_skill_source_entries_source_id_company_skill_sources_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skill_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_source_entries_skill_id_company_skills_id_fk": { + "name": "company_skill_source_entries_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_source_entries", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_sources": { + "name": "company_skill_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_url": { + "name": "repository_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_ref": { + "name": "tracking_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "package_mode": { + "name": "package_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skills'" + }, + "repository_package": { + "name": "repository_package", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_folders": { + "name": "excluded_folders", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_scan_commit": { + "name": "last_scan_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_sources_repository_ref_idx": { + "name": "company_skill_sources_repository_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_sources_identity_ref_idx": { + "name": "company_skill_sources_identity_ref_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tracking_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_sources_company_id_companies_id_fk": { + "name": "company_skill_sources_company_id_companies_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_sources_connection_id_tool_connections_id_fk": { + "name": "company_skill_sources_connection_id_tool_connections_id_fk", + "tableFrom": "company_skill_sources", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_repository_snapshots": { + "name": "company_skill_repository_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_repository_snapshots_content_idx": { + "name": "company_skill_repository_snapshots_content_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_repository_snapshots_company_id_companies_id_fk": { + "name": "company_skill_repository_snapshots_company_id_companies_id_fk", + "tableFrom": "company_skill_repository_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_snapshot_id": { + "name": "repository_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_skill_path": { + "name": "repository_skill_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk": { + "name": "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skill_repository_snapshots", + "columnsFrom": [ + "repository_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_event_deliveries": { + "name": "connection_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_delivery_id": { + "name": "provider_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_payload": { + "name": "normalized_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_event_deliveries_company_provider_id_uq": { + "name": "connection_event_deliveries_company_provider_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_event_deliveries_company_status_idx": { + "name": "connection_event_deliveries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_event_deliveries_company_id_companies_id_fk": { + "name": "connection_event_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_event_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_intent_deliveries": { + "name": "connection_intent_deliveries", + "schema": "", + "columns": { + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_intent_deliveries_pending_idx": { + "name": "connection_intent_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_intent_deliveries_company_id_companies_id_fk": { + "name": "connection_intent_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_kind": { + "name": "usage_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reported_cost_cents": { + "name": "reported_cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": false + }, + "pricing_provenance": { + "name": "pricing_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_receipt_idx": { + "name": "cost_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_provider_request_idx": { + "name": "cost_events_provider_request_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_project_occurred_idx": { + "name": "cost_events_company_project_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_unpriced_idx": { + "name": "cost_events_unpriced_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_data_quality_idx": { + "name": "cost_events_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cost_events\".\"billing_type\" = 'unknown' or (\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_subscription_occurred_idx": { + "name": "cost_events_company_subscription_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_subscription_id_ai_subscriptions_id_fk": { + "name": "cost_events_subscription_id_ai_subscriptions_id_fk", + "tableFrom": "cost_events", + "tableTo": "ai_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cost_events_usage_kind_check": { + "name": "cost_events_usage_kind_check", + "value": "\"cost_events\".\"usage_kind\" in ('decision', 'fast_response') or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)" + }, + "cost_events_nonnegative_amounts": { + "name": "cost_events_nonnegative_amounts", + "value": "\"cost_events\".\"cost_cents\" >= 0 and \"cost_events\".\"input_tokens\" >= 0 and \"cost_events\".\"cached_input_tokens\" >= 0 and \"cost_events\".\"output_tokens\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.company_decision_models": { + "name": "company_decision_models", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_background": { + "name": "allow_background", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_decision_models_company_id_companies_id_fk": { + "name": "company_decision_models_company_id_companies_id_fk", + "tableFrom": "company_decision_models", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_decisions_configured": { + "name": "company_decisions_configured", + "value": "(\"company_decision_models\".\"connection_id\" is null) = (\"company_decision_models\".\"grant_id\" is null) and (not \"company_decision_models\".\"enabled\" or (\"company_decision_models\".\"connection_id\" is not null and \"company_decision_models\".\"provider\" is not null and \"company_decision_models\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.decision_invocations": { + "name": "decision_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "identity_context_id": { + "name": "identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "question_types": { + "name": "question_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_invocations_company_started_idx": { + "name": "decision_invocations_company_started_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "decision_invocations_pending_idx": { + "name": "decision_invocations_pending_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"decision_invocations\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_invocations_cost_idx": { + "name": "decision_invocations_cost_idx", + "columns": [ + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_invocations_company_id_companies_id_fk": { + "name": "decision_invocations_company_id_companies_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_invocations_cost_event_id_cost_events_id_fk": { + "name": "decision_invocations_cost_event_id_cost_events_id_fk", + "tableFrom": "decision_invocations", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_invocations_status_check": { + "name": "decision_invocations_status_check", + "value": "\"decision_invocations\".\"status\" in ('running', 'succeeded', 'failed', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_agent_bindings": { + "name": "dot_agent_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operator_id": { + "name": "operator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pairing'" + }, + "pairing_code_hash": { + "name": "pairing_code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pairing_expires_at": { + "name": "pairing_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dot_url": { + "name": "dot_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_hash": { + "name": "challenge_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge_expires_at": { + "name": "challenge_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ready_at": { + "name": "ready_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_bindings_active_agent_uq": { + "name": "dot_bindings_active_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_active_grant_uq": { + "name": "dot_bindings_active_grant_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_agent_bindings\".\"revoked_at\" IS NULL AND \"dot_agent_bindings\".\"grant_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_bindings_pairing_code_uq": { + "name": "dot_bindings_pairing_code_uq", + "columns": [ + { + "expression": "pairing_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_agent_bindings_company_id_companies_id_fk": { + "name": "dot_agent_bindings_company_id_companies_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_agent_id_agents_id_fk": { + "name": "dot_agent_bindings_agent_id_agents_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk": { + "name": "dot_agent_bindings_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "dot_agent_bindings", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_mailbox_items": { + "name": "dot_mailbox_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "references": { + "name": "references", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_mailbox_event_uq": { + "name": "dot_mailbox_event_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_mailbox_binding_cursor_idx": { + "name": "dot_mailbox_binding_cursor_idx", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_mailbox_items_company_id_companies_id_fk": { + "name": "dot_mailbox_items_company_id_companies_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_mailbox_items_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_mailbox_items_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_mailbox_items", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_assignments": { + "name": "dot_runner_assignments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binding_generation": { + "name": "binding_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "normalized_session_id": { + "name": "normalized_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "catalog_digest": { + "name": "catalog_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "projection": { + "name": "projection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "accept_by": { + "name": "accept_by", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_activity_at": { + "name": "last_activity_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()" + } + }, + "indexes": { + "dot_assignments_turn_uq": { + "name": "dot_assignments_turn_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_active_binding_uq": { + "name": "dot_assignments_active_binding_uq", + "columns": [ + { + "expression": "binding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"dot_runner_assignments\".\"status\" IN ('offered', 'accepted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "dot_assignments_company_idx": { + "name": "dot_assignments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_assignments_company_id_companies_id_fk": { + "name": "dot_runner_assignments_company_id_companies_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk": { + "name": "dot_runner_assignments_binding_id_dot_agent_bindings_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "dot_agent_bindings", + "columnsFrom": [ + "binding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_run_id_heartbeat_runs_id_fk": { + "name": "dot_runner_assignments_run_id_heartbeat_runs_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_assignments_agent_id_agents_id_fk": { + "name": "dot_runner_assignments_agent_id_agents_id_fk", + "tableFrom": "dot_runner_assignments", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dot_runner_operations": { + "name": "dot_runner_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "outcome": { + "name": "outcome", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dot_operations_request_uq": { + "name": "dot_operations_request_uq", + "columns": [ + { + "expression": "assignment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dot_runner_operations_company_id_companies_id_fk": { + "name": "dot_runner_operations_company_id_companies_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk": { + "name": "dot_runner_operations_assignment_id_dot_runner_assignments_id_fk", + "tableFrom": "dot_runner_operations", + "tableTo": "dot_runner_assignments", + "columnsFrom": [ + "assignment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_endpoints": { + "name": "email_endpoints", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receive_mode": { + "name": "receive_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_api_key_id": { + "name": "owned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_at": { + "name": "activation_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_endpoints", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_endpoints_receive_mode_check": { + "name": "email_endpoints_receive_mode_check", + "value": "\"email_endpoints\".\"receive_mode\" in ('websocket', 'webhook')" + } + }, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatic": { + "name": "automatic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_messages_provider_uq": { + "name": "email_messages_provider_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_messages_conversation_idx": { + "name": "email_messages_conversation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_messages_direction_check": { + "name": "email_messages_direction_check", + "value": "\"email_messages\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.email_sends": { + "name": "email_sends", + "schema": "", + "columns": { + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "first_attempt_at": { + "name": "first_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_sends_pending_idx": { + "name": "email_sends_pending_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"email_sends\".\"outcome\" in ('queued', 'uncertain')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_sends_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "email_sends_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_sends_outcome_check": { + "name": "email_sends_outcome_check", + "value": "\"email_sends\".\"outcome\" in ('queued', 'sent', 'delivered', 'failed', 'uncertain')" + } + }, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repositories": { + "name": "execution_workspace_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_issue_id": { + "name": "requested_by_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_identity": { + "name": "repository_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "catalog_repository_id": { + "name": "catalog_repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "relative_path": { + "name": "relative_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_ref": { + "name": "requested_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'HEAD'" + }, + "pinned_commit": { + "name": "pinned_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_keys": { + "name": "request_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_repositories_identity_idx": { + "name": "execution_workspace_repositories_identity_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_identity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_path_idx": { + "name": "execution_workspace_repositories_path_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "relative_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_request_idx": { + "name": "execution_workspace_repositories_request_idx", + "columns": [ + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_repositories_company_workspace_idx": { + "name": "execution_workspace_repositories_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repositories_company_id_companies_id_fk": { + "name": "execution_workspace_repositories_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_repositories_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_repositories_requested_by_issue_id_issues_id_fk": { + "name": "execution_workspace_repositories_requested_by_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_repositories", + "tableTo": "issues", + "columnsFrom": [ + "requested_by_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repository_access": { + "name": "execution_workspace_repository_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_key": { + "name": "root_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_cwd": { + "name": "canonical_cwd", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_repository_access_scope_idx": { + "name": "execution_workspace_repository_access_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repository_access_company_id_companies_id_fk": { + "name": "execution_workspace_repository_access_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repository_access", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_repository_roots": { + "name": "execution_workspace_repository_roots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "canonical_cwd": { + "name": "canonical_cwd", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "root_key": { + "name": "root_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "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": { + "execution_workspace_repository_roots_company_root_idx": { + "name": "execution_workspace_repository_roots_company_root_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_repository_roots_company_id_companies_id_fk": { + "name": "execution_workspace_repository_roots_company_id_companies_id_fk", + "tableFrom": "execution_workspace_repository_roots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_fast_responses": { + "name": "company_fast_responses", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_sponsored": { + "name": "allow_sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_fast_responses_company_id_companies_id_fk": { + "name": "company_fast_responses_company_id_companies_id_fk", + "tableFrom": "company_fast_responses", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_fast_responses_configured": { + "name": "company_fast_responses_configured", + "value": "(\"company_fast_responses\".\"connection_id\" is null) = (\"company_fast_responses\".\"grant_id\" is null) and (not \"company_fast_responses\".\"enabled\" or (\"company_fast_responses\".\"connection_id\" is not null and \"company_fast_responses\".\"model\" is not null))" + } + }, + "isRLSEnabled": false + }, + "public.fast_response_requests": { + "name": "fast_response_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sponsored": { + "name": "sponsored", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "publication_status": { + "name": "publication_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_request_id": { + "name": "provider_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_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 + }, + "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 + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "fast_response_requests_source_idx": { + "name": "fast_response_requests_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_history_idx": { + "name": "fast_response_requests_history_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "fast_response_requests_work_idx": { + "name": "fast_response_requests_work_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"fast_response_requests\".\"status\" in ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fast_response_requests_company_id_companies_id_fk": { + "name": "fast_response_requests_company_id_companies_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fast_response_requests_cost_event_id_cost_events_id_fk": { + "name": "fast_response_requests_cost_event_id_cost_events_id_fk", + "tableFrom": "fast_response_requests", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "fast_response_requests_status_check": { + "name": "fast_response_requests_status_check", + "value": "\"fast_response_requests\".\"status\" in ('pending', 'running', 'succeeded', 'failed', 'unknown', 'skipped')" + } + }, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_hash": { + "name": "receipt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_receipt_idx": { + "name": "finance_events_company_receipt_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_identity_context_id": { + "name": "active_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_control_deadline_at": { + "name": "execution_control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status_delivery_id": { + "name": "execution_status_delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_accounting_pending": { + "name": "cost_accounting_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_accounted_at": { + "name": "cost_accounted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_projection_version": { + "name": "accounting_projection_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_last_attempt_at": { + "name": "accounting_last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accounting_last_error": { + "name": "accounting_last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accounting_attempt_count": { + "name": "accounting_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "controller_lease_expires_at": { + "name": "controller_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_stage": { + "name": "execution_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_cost_accounting_pending_idx": { + "name": "heartbeat_runs_cost_accounting_pending_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_cost_data_quality_idx": { + "name": "heartbeat_runs_cost_data_quality_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"finished_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true and \"heartbeat_runs\".\"status\" in ('succeeded','failed','timed_out','cancelled','interrupted')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_status_delivery_idx": { + "name": "heartbeat_runs_execution_status_delivery_idx", + "columns": [ + { + "expression": "execution_status_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_status_delivery_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_control_deadline_idx": { + "name": "heartbeat_runs_execution_control_deadline_idx", + "columns": [ + { + "expression": "execution_control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_native_replacement_predecessor_uq": { + "name": "heartbeat_runs_native_replacement_predecessor_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_of_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_runs\".\"scheduled_retry_reason\" = 'native_safe_replacement'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_issue_created_idx": { + "name": "heartbeat_runs_company_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_issue_id_issues_id_fk": { + "name": "heartbeat_runs_issue_id_issues_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "heartbeat_runs_scope_binding_check": { + "name": "heartbeat_runs_scope_binding_check", + "value": "(\"heartbeat_runs\".\"scope_kind\" = 'company' AND \"heartbeat_runs\".\"issue_id\" IS NULL)\n OR \"heartbeat_runs\".\"scope_kind\" = 'issue'" + } + }, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_agent_idx": { + "name": "connection_grants_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_agent_uq": { + "name": "connection_grants_agent_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_subject_agent_id_agents_id_fk": { + "name": "connection_grants_subject_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user', 'agent')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null and \"connection_grants\".\"subject_agent_id\" is null) or (\"connection_grants\".\"kind\" = 'agent' and \"connection_grants\".\"subject_agent_id\" is not null and \"connection_grants\".\"subject_user_id\" is null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null and \"connection_grants\".\"subject_agent_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_access_grants": { + "name": "issue_access_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by_agent_id": { + "name": "granted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "issue_access_grants_subject_active_issue_idx": { + "name": "issue_access_grants_subject_active_issue_idx", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_access_grants_issue_idx": { + "name": "issue_access_grants_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_access_grants_issue_id_issues_id_fk": { + "name": "issue_access_grants_issue_id_issues_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_access_grants_granted_by_agent_id_agents_id_fk": { + "name": "issue_access_grants_granted_by_agent_id_agents_id_fk", + "tableFrom": "issue_access_grants", + "tableTo": "agents", + "columnsFrom": [ + "granted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_access_grants_subject_type_check": { + "name": "issue_access_grants_subject_type_check", + "value": "\"issue_access_grants\".\"subject_type\" in ('user', 'agent')" + }, + "issue_access_grants_source_check": { + "name": "issue_access_grants_source_check", + "value": "\"issue_access_grants\".\"source\" in ('explicit', 'assignment', 'project')" + } + }, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "originating_run_id": { + "name": "originating_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_originating_run_idx": { + "name": "issue_attachments_originating_run_idx", + "columns": [ + { + "expression": "originating_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_attachments_originating_run_id_heartbeat_runs_id_fk": { + "name": "issue_attachments_originating_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "originating_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'comment'" + }, + "fast_response_request_id": { + "name": "fast_response_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issue_comments_client_request_uq": { + "name": "issue_comments_client_request_uq", + "nullsNotDistinct": false, + "columns": [ + "issue_id", + "author_user_id", + "client_request_id" + ] + }, + "issue_comments_company_id_uq": { + "name": "issue_comments_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_comment_ids": { + "name": "origin_comment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_identity_context_id": { + "name": "source_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_agent_id": { + "name": "conversation_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_user_id": { + "name": "conversation_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_state": { + "name": "conversation_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "conversation_boundary_comment_id": { + "name": "conversation_boundary_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_root_issue_id": { + "name": "privacy_root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "privacy_parent_issue_id": { + "name": "privacy_parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_needs_generation": { + "name": "title_needs_generation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_identity_context_id": { + "name": "origin_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "continuation_identity_context_id": { + "name": "continuation_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_binding_revision": { + "name": "workspace_binding_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "workspace_selection": { + "name": "workspace_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "workspace_pending_selection": { + "name": "workspace_pending_selection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_conversation_identity_idx": { + "name": "issues_conversation_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_parent_idx": { + "name": "issues_company_privacy_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_parent_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_privacy_root_idx": { + "name": "issues_company_privacy_root_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "privacy_root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_conversation_agent_id_agents_id_fk": { + "name": "issues_conversation_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "conversation_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_root_issue_id_issues_id_fk": { + "name": "issues_privacy_root_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_privacy_parent_issue_id_issues_id_fk": { + "name": "issues_privacy_parent_issue_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "privacy_parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "issues_visibility_check": { + "name": "issues_visibility_check", + "value": "\"issues\".\"visibility\" in ('open', 'private')" + }, + "issues_conversation_identity_check": { + "name": "issues_conversation_identity_check", + "value": "(\n \"issues\".\"conversation_agent_id\" is null and \"issues\".\"conversation_user_id\" is null and \"issues\".\"conversation_state\" is null\n ) or (\n \"issues\".\"conversation_agent_id\" is not null and \"issues\".\"conversation_user_id\" is not null\n and \"issues\".\"assignee_agent_id\" = \"issues\".\"conversation_agent_id\" and \"issues\".\"assignee_agent_id\" is not null\n and \"issues\".\"assignee_user_id\" is null and \"issues\".\"conversation_state\" is not null\n and \"issues\".\"conversation_state\" in ('active', 'waiting')\n and \"issues\".\"status\" not in ('done', 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_pid": { + "name": "controller_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "controller_process_started_at": { + "name": "controller_process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recovery_state": { + "name": "recovery_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_request_id": { + "name": "recovery_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_history": { + "name": "recovery_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "control_deadline_at": { + "name": "control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_finalizations_control_deadline_idx": { + "name": "native_run_finalizations_control_deadline_idx", + "columns": [ + { + "expression": "control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"native_run_finalizations\".\"control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_lifecycle_acknowledgments": { + "name": "plugin_lifecycle_acknowledgments", + "schema": "", + "columns": { + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_lifecycle_acknowledgments_event_idx": { + "name": "plugin_lifecycle_acknowledgments_event_idx", + "columns": [ + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_plugins_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk": { + "name": "plugin_lifecycle_acknowledgments_event_id_resource_lifecycle_events_id_fk", + "tableFrom": "plugin_lifecycle_acknowledgments", + "tableTo": "resource_lifecycle_events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk": { + "name": "plugin_lifecycle_acknowledgments_plugin_id_event_id_pk", + "columns": [ + "plugin_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_access_members": { + "name": "project_access_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_access_members_project_subject_uq": { + "name": "project_access_members_project_subject_uq", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_access_members_subject_lookup_idx": { + "name": "project_access_members_subject_lookup_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_access_members_company_id_companies_id_fk": { + "name": "project_access_members_company_id_companies_id_fk", + "tableFrom": "project_access_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_access_members_project_id_projects_id_fk": { + "name": "project_access_members_project_id_projects_id_fk", + "tableFrom": "project_access_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "project_access_members_subject_type_check": { + "name": "project_access_members_subject_type_check", + "value": "\"project_access_members\".\"subject_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "privacy_owner_user_id": { + "name": "privacy_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "personal_owner_user_id": { + "name": "personal_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "projects_company_personal_owner_uq": { + "name": "projects_company_personal_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "personal_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"projects\".\"personal_owner_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "projects_visibility_check": { + "name": "projects_visibility_check", + "value": "\"projects\".\"visibility\" in ('open', 'private')" + } + }, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.resource_lifecycle_events": { + "name": "resource_lifecycle_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "resource_lifecycle_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_lifecycle_events_creation_idx": { + "name": "resource_lifecycle_events_creation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"resource_lifecycle_events\".\"action\" = 'create'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_company_idx": { + "name": "resource_lifecycle_events_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_lifecycle_events_resource_order_idx": { + "name": "resource_lifecycle_events_resource_order_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_lifecycle_events_company_id_companies_id_fk": { + "name": "resource_lifecycle_events_company_id_companies_id_fk", + "tableFrom": "resource_lifecycle_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_lifecycle_events_resource_type_check": { + "name": "resource_lifecycle_events_resource_type_check", + "value": "\"resource_lifecycle_events\".\"resource_type\" IN ('agent', 'project')" + }, + "resource_lifecycle_events_action_check": { + "name": "resource_lifecycle_events_action_check", + "value": "\"resource_lifecycle_events\".\"action\" = 'create' OR (\"resource_lifecycle_events\".\"resource_type\" = 'project' AND \"resource_lifecycle_events\".\"action\" IN ('update', 'archive')) OR (\"resource_lifecycle_events\".\"resource_type\" = 'agent' AND \"resource_lifecycle_events\".\"action\" IN ('pause', 'resume', 'terminate'))" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "setup_pending": { + "name": "setup_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_webhook_delivery": { + "name": "last_webhook_delivery", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_webhook_test_receipts": { + "name": "routine_webhook_test_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_key_hash": { + "name": "delivery_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_webhook_test_receipts_delivery_uq": { + "name": "routine_webhook_test_receipts_delivery_uq", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_webhook_test_receipts_company_id_companies_id_fk": { + "name": "routine_webhook_test_receipts_company_id_companies_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk": { + "name": "routine_webhook_test_receipts_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_webhook_test_receipts", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.run_identity_contexts": { + "name": "run_identity_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_context_id": { + "name": "parent_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_identity_contexts_run_revision_idx": { + "name": "run_identity_contexts_run_revision_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_run_correlation_idx": { + "name": "run_identity_contexts_run_correlation_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_company_run_idx": { + "name": "run_identity_contexts_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_identity_contexts_company_id_companies_id_fk": { + "name": "run_identity_contexts_company_id_companies_id_fk", + "tableFrom": "run_identity_contexts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runner_api_response_reservations": { + "name": "runner_api_response_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reserved_bytes": { + "name": "reserved_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "runner_api_response_reservations_company_idx": { + "name": "runner_api_response_reservations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runner_api_response_reservations_company_id_companies_id_fk": { + "name": "runner_api_response_reservations_company_id_companies_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runner_api_response_reservations_run_id_heartbeat_runs_id_fk": { + "name": "runner_api_response_reservations_run_id_heartbeat_runs_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "runner_api_response_reservations_asset_id_assets_id_fk": { + "name": "runner_api_response_reservations_asset_id_assets_id_fk", + "tableFrom": "runner_api_response_reservations", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "numeric(24, 7)", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_deliveries": { + "name": "tool_action_deliveries", + "schema": "", + "columns": { + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_deliveries_pending_idx": { + "name": "tool_action_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_deliveries_action_request_id_tool_action_requests_id_fk": { + "name": "tool_action_deliveries_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_company_id_companies_id_fk": { + "name": "tool_action_deliveries_company_id_companies_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_issue_id_issues_id_fk": { + "name": "tool_action_deliveries_issue_id_issues_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_snapshots": { + "name": "tool_connection_app_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "toolkit": { + "name": "toolkit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accounts": { + "name": "accounts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error_at": { + "name": "error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "tool_connection_app_snapshots_owner_toolkit_uq": { + "name": "tool_connection_app_snapshots_owner_toolkit_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "toolkit", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_snapshots_company_id_companies_id_fk": { + "name": "tool_connection_app_snapshots_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_snapshots_company_connection_fk": { + "name": "tool_connection_app_snapshots_company_connection_fk", + "tableFrom": "tool_connection_app_snapshots", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_app_syncs": { + "name": "tool_connection_app_syncs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_key": { + "name": "credential_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_id": { + "name": "lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checked": { + "name": "checked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total": { + "name": "total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_app_syncs_owner_key_uq": { + "name": "tool_connection_app_syncs_owner_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_app_syncs_company_id_companies_id_fk": { + "name": "tool_connection_app_syncs_company_id_companies_id_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_app_syncs_company_connection_fk": { + "name": "tool_connection_app_syncs_company_connection_fk", + "tableFrom": "tool_connection_app_syncs", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "connection_purpose": { + "name": "connection_purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_instructions": { + "name": "agent_instructions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'voice', 'runtime_auth')" + }, + "tool_connections_purpose_check": { + "name": "tool_connections_purpose_check", + "value": "\"tool_connections\".\"connection_purpose\" in ('tool', 'channel', 'ai')" + }, + "tool_connections_channel_transport_check": { + "name": "tool_connections_channel_transport_check", + "value": "(\n (\"tool_connections\".\"connection_purpose\" = 'tool' and \"tool_connections\".\"transport\" not in ('chat_sdk', 'voice', 'runtime_auth'))\n or\n (\"tool_connections\".\"connection_purpose\" = 'channel' and (\"tool_connections\".\"transport\" in ('chat_sdk', 'voice') or (\"tool_connections\".\"transport\" = 'rest_api' and \"tool_connections\".\"config\"->>'provider' = 'agentmail')))\n or\n (\"tool_connections\".\"connection_purpose\" = 'ai' and \"tool_connections\".\"transport\" = 'runtime_auth')\n )" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback', 'per_agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_expiry_idx": { + "name": "tool_mcp_gateway_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_subject_agent_idx": { + "name": "tool_oauth_states_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_subject_agent_id_agents_id_fk": { + "name": "tool_oauth_states_subject_agent_id_agents_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_company_preferences": { + "name": "user_company_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_agent_id": { + "name": "primary_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "primary_agent_initialized": { + "name": "primary_agent_initialized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_company_preferences_company_user_uq": { + "name": "user_company_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_company_preferences_primary_agent_idx": { + "name": "user_company_preferences_primary_agent_idx", + "columns": [ + { + "expression": "primary_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_company_preferences_company_id_companies_id_fk": { + "name": "user_company_preferences_company_id_companies_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_company_preferences_primary_agent_id_agents_id_fk": { + "name": "user_company_preferences_primary_agent_id_agents_id_fk", + "tableFrom": "user_company_preferences", + "tableTo": "agents", + "columnsFrom": [ + "primary_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_attachment_uploads": { + "name": "mcp_attachment_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cleaned_at": { + "name": "cleaned_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": { + "mcp_attachment_upload_request_uq": { + "name": "mcp_attachment_upload_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_attachment_upload_expiry_idx": { + "name": "mcp_attachment_upload_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_attachment_uploads_company_id_companies_id_fk": { + "name": "mcp_attachment_uploads_company_id_companies_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_attachment_uploads_user_id_user_id_fk": { + "name": "mcp_attachment_uploads_user_id_user_id_fk", + "tableFrom": "mcp_attachment_uploads", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_admissions": { + "name": "mcp_event_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reserves_subscription": { + "name": "reserves_subscription", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "mcp_event_admissions_expiry_idx": { + "name": "mcp_event_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_grant_idx": { + "name": "mcp_event_admissions_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_admissions_company_idx": { + "name": "mcp_event_admissions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_admissions_company_id_companies_id_fk": { + "name": "mcp_event_admissions_company_id_companies_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_admissions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_admissions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_deliveries": { + "name": "mcp_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activity_id": { + "name": "activity_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mailbox_item_id": { + "name": "mailbox_item_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_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 + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "mcp_event_deliveries_mailbox_uq": { + "name": "mcp_event_deliveries_mailbox_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mailbox_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_activity_uq": { + "name": "mcp_event_deliveries_activity_uq", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_deliveries_due_idx": { + "name": "mcp_event_deliveries_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk": { + "name": "mcp_event_deliveries_subscription_id_mcp_event_subscriptions_id_fk", + "tableFrom": "mcp_event_deliveries", + "tableTo": "mcp_event_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_delivery_source_check": { + "name": "mcp_delivery_source_check", + "value": "(\"mcp_event_deliveries\".\"activity_id\" IS NOT NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NULL) OR (\"mcp_event_deliveries\".\"activity_id\" IS NULL AND \"mcp_event_deliveries\".\"mailbox_item_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_event_subscriptions": { + "name": "mcp_event_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "binding_id": { + "name": "binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "arguments": { + "name": "arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_material": { + "name": "delivery_material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_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 + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scanned_at": { + "name": "scanned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_event_subscriptions_expiry_idx": { + "name": "mcp_event_subscriptions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_event_subscriptions_company_idx": { + "name": "mcp_event_subscriptions_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_subscriptions_company_id_companies_id_fk": { + "name": "mcp_event_subscriptions_company_id_companies_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_event_subscriptions_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_subscription_resource_check": { + "name": "mcp_subscription_resource_check", + "value": "(\"mcp_event_subscriptions\".\"task_id\" IS NOT NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NULL) OR (\"mcp_event_subscriptions\".\"task_id\" IS NULL AND \"mcp_event_subscriptions\".\"binding_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.mcp_file_tickets": { + "name": "mcp_file_tickets", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "upload_id": { + "name": "upload_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_file_ticket_expiry_idx": { + "name": "mcp_file_ticket_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_file_ticket_grant_idx": { + "name": "mcp_file_ticket_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_file_tickets_company_id_companies_id_fk": { + "name": "mcp_file_tickets_company_id_companies_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_file_tickets_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk": { + "name": "mcp_file_tickets_upload_id_mcp_attachment_uploads_id_fk", + "tableFrom": "mcp_file_tickets", + "tableTo": "mcp_attachment_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_mutation_receipts": { + "name": "mcp_mutation_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reserved'" + }, + "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()" + } + }, + "indexes": { + "mcp_mutation_receipts_request_uq": { + "name": "mcp_mutation_receipts_request_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_mutation_receipts_company_idx": { + "name": "mcp_mutation_receipts_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_mutation_receipts_company_id_companies_id_fk": { + "name": "mcp_mutation_receipts_company_id_companies_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_user_id_user_id_fk": { + "name": "mcp_mutation_receipts_user_id_user_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_mutation_receipts_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_mutation_receipts", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_clients": { + "name": "mcp_oauth_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "registration_source_hash": { + "name": "registration_source_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "grant_types": { + "name": "grant_types", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"authorization_code\",\"refresh_token\"]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_device_requests": { + "name": "mcp_oauth_device_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code_hash": { + "name": "user_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interval_seconds": { + "name": "interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "next_poll_at": { + "name": "next_poll_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_device_code_uq": { + "name": "mcp_oauth_device_code_uq", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_user_code_uq": { + "name": "mcp_oauth_user_code_uq", + "columns": [ + { + "expression": "user_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_device_expiry_idx": { + "name": "mcp_oauth_device_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_device_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_device_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_device_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_grants": { + "name": "mcp_oauth_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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()" + } + }, + "indexes": { + "mcp_oauth_grants_user_company_idx": { + "name": "mcp_oauth_grants_user_company_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_grants_company_id_companies_id_fk": { + "name": "mcp_oauth_grants_company_id_companies_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_user_id_user_id_fk": { + "name": "mcp_oauth_grants_user_id_user_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_grants_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_grants_agent_id_agents_id_fk": { + "name": "mcp_oauth_grants_agent_id_agents_id_fk", + "tableFrom": "mcp_oauth_grants", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_metadata_admissions": { + "name": "mcp_oauth_metadata_admissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_oauth_metadata_admissions_expiry_idx": { + "name": "mcp_oauth_metadata_admissions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_requests": { + "name": "mcp_oauth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_requests_code_uq": { + "name": "mcp_oauth_requests_code_uq", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_requests_expiry_idx": { + "name": "mcp_oauth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk": { + "name": "mcp_oauth_requests_client_id_mcp_oauth_clients_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_requests_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_requests", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_tokens": { + "name": "mcp_oauth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_tokens_hash_uq": { + "name": "mcp_oauth_tokens_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_grant_idx": { + "name": "mcp_oauth_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_tokens_expiry_idx": { + "name": "mcp_oauth_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk": { + "name": "mcp_oauth_tokens_grant_id_mcp_oauth_grants_id_fk", + "tableFrom": "mcp_oauth_tokens", + "tableTo": "mcp_oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": { + "public.chat_telegram_draft_ids": { + "name": "chat_telegram_draft_ids", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 22c832f0017..cf354e19e94 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -2339,6 +2339,27 @@ "when": 1791832982875, "tag": "0335_absurd_bulldozer", "breakpoints": true + }, + { + "idx": 336, + "version": "7", + "when": 1791837392742, + "tag": "0336_big_crystal", + "breakpoints": true + }, + { + "idx": 337, + "version": "7", + "when": 1791837407237, + "tag": "0337_exotic_proemial_gods", + "breakpoints": true + }, + { + "idx": 338, + "version": "7", + "when": 1791837431016, + "tag": "0338_absurd_vengeance", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/chat_channels.ts b/packages/db/src/schema/chat_channels.ts index 22b175e0e6f..9f83918b3d8 100644 --- a/packages/db/src/schema/chat_channels.ts +++ b/packages/db/src/schema/chat_channels.ts @@ -14,6 +14,7 @@ import { uuid, } from "drizzle-orm/pg-core"; import type { + ChatExecutionDefaults, ChatAdapterCapabilities, ChatConcurrencyPolicy, ChatDeliveryState, @@ -65,6 +66,7 @@ export const chatEndpoints = pgTable( botDisplayName: text("bot_display_name"), botAvatarUrl: text("bot_avatar_url"), communicationInstructions: text("communication_instructions").notNull().default(""), + executionDefaults: jsonb("execution_defaults").$type(), allowDirectMessages: boolean("allow_direct_messages") .notNull() .default(true), @@ -220,6 +222,7 @@ export const chatEndpointResources = pgTable( .notNull() .default("available"), enabled: boolean("enabled").notNull().default(false), + executionDefaults: jsonb("execution_defaults").$type(), metadata: jsonb("metadata") .$type>() .notNull() diff --git a/packages/db/src/schema/execution_workspace_repositories.ts b/packages/db/src/schema/execution_workspace_repositories.ts new file mode 100644 index 00000000000..5ee96ff9633 --- /dev/null +++ b/packages/db/src/schema/execution_workspace_repositories.ts @@ -0,0 +1,29 @@ +import { index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; +import { executionWorkspaces } from "./execution_workspaces.js"; +import { issues } from "./issues.js"; + +export const executionWorkspaceRepositories = pgTable("execution_workspace_repositories", { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + executionWorkspaceId: uuid("execution_workspace_id").notNull().references(() => executionWorkspaces.id, { onDelete: "cascade" }), + requestedByIssueId: uuid("requested_by_issue_id").references(() => issues.id, { onDelete: "set null" }), + repositoryIdentity: text("repository_identity").notNull(), + catalogRepositoryId: text("catalog_repository_id"), + repoUrl: text("repo_url").notNull(), + relativePath: text("relative_path").notNull(), + requestedRef: text("requested_ref").notNull().default("HEAD"), + pinnedCommit: text("pinned_commit"), + branchName: text("branch_name"), + requestKey: text("request_key").notNull(), + requestKeys: jsonb("request_keys").$type().notNull().default([]), + state: text("state").notNull().default("pending"), + failureCode: text("failure_code"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, (table) => ({ + identityIdx: uniqueIndex("execution_workspace_repositories_identity_idx").on(table.executionWorkspaceId, table.repositoryIdentity), + pathIdx: uniqueIndex("execution_workspace_repositories_path_idx").on(table.executionWorkspaceId, table.relativePath), + requestIdx: uniqueIndex("execution_workspace_repositories_request_idx").on(table.executionWorkspaceId, table.requestKey), + companyWorkspaceIdx: index("execution_workspace_repositories_company_workspace_idx").on(table.companyId, table.executionWorkspaceId), +})); diff --git a/packages/db/src/schema/execution_workspace_repository_access.ts b/packages/db/src/schema/execution_workspace_repository_access.ts new file mode 100644 index 00000000000..af1a2cd1f11 --- /dev/null +++ b/packages/db/src/schema/execution_workspace_repository_access.ts @@ -0,0 +1,18 @@ +import { pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; + +export const executionWorkspaceRepositoryAccess = pgTable("execution_workspace_repository_access", { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + rootKey: text("root_key").notNull(), + canonicalCwd: text("canonical_cwd").notNull(), + kind: text("kind").$type<"source" | "reader" | "project_source" | "shared_project_source" | "isolated_source" | "isolated_project_source" | "isolated_copy" | "repository">().notNull(), + scopeId: uuid("scope_id").notNull(), + issueId: uuid("issue_id"), + agentId: uuid("agent_id"), + responsibleUserId: text("responsible_user_id"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, (table) => ({ + scopeIdx: uniqueIndex("execution_workspace_repository_access_scope_idx").on(table.companyId, table.rootKey, table.kind, table.scopeId), +})); diff --git a/packages/db/src/schema/execution_workspace_repository_roots.ts b/packages/db/src/schema/execution_workspace_repository_roots.ts new file mode 100644 index 00000000000..3aae21bef6d --- /dev/null +++ b/packages/db/src/schema/execution_workspace_repository_roots.ts @@ -0,0 +1,14 @@ +import { pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"; +import { companies } from "./companies.js"; + +export const executionWorkspaceRepositoryRoots = pgTable("execution_workspace_repository_roots", { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + canonicalCwd: text("canonical_cwd").notNull(), + rootKey: text("root_key").notNull(), + mode: text("mode").$type<"anonymous_only" | "managed">().notNull(), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}, (table) => ({ + rootIdx: uniqueIndex("execution_workspace_repository_roots_company_root_idx").on(table.companyId, table.rootKey), +})); diff --git a/packages/db/src/schema/execution_workspaces.ts b/packages/db/src/schema/execution_workspaces.ts index 6d012445bde..6e3f8b01331 100644 --- a/packages/db/src/schema/execution_workspaces.ts +++ b/packages/db/src/schema/execution_workspaces.ts @@ -17,7 +17,7 @@ export const executionWorkspaces = pgTable( { id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), - projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }), + projectId: uuid("project_id").references(() => projects.id, { onDelete: "restrict" }), projectWorkspaceId: uuid("project_workspace_id").references(() => projectWorkspaces.id, { onDelete: "set null" }), sourceIssueId: uuid("source_issue_id").references((): AnyPgColumn => issues.id, { onDelete: "set null" }), mode: text("mode").notNull(), diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 630a0869b55..edad903b628 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -247,4 +247,7 @@ export { userCompanyPreferences } from "./user_company_preferences.js"; export { chatSlackManagerGrants } from "./chat_slack_manager_grants.js"; export { aiSubscriptions, aiSubscriptionPrices, aiSubscriptionConnections } from "./ai_subscriptions.js"; +export { executionWorkspaceRepositories } from "./execution_workspace_repositories.js"; +export { executionWorkspaceRepositoryRoots } from "./execution_workspace_repository_roots.js"; +export { executionWorkspaceRepositoryAccess } from "./execution_workspace_repository_access.js"; export * from "./fast_responses.js"; diff --git a/packages/db/src/schema/issues.ts b/packages/db/src/schema/issues.ts index a4384709ed8..1f9b61dd22c 100644 --- a/packages/db/src/schema/issues.ts +++ b/packages/db/src/schema/issues.ts @@ -21,7 +21,7 @@ import { companies } from "./companies.js"; import { heartbeatRuns } from "./heartbeat_runs.js"; import { projectWorkspaces } from "./project_workspaces.js"; import { executionWorkspaces } from "./execution_workspaces.js"; -import type { IssueReviewPolicy, IssueUnblockDescriptor, SourceTrustMetadata } from "@paperclipai/shared"; +import type { IssueReviewPolicy, IssueUnblockDescriptor, SourceTrustMetadata, TaskWorkspaceIntent, TaskWorkspacePendingSelection } from "@paperclipai/shared"; export const issues = pgTable( "issues", @@ -81,6 +81,9 @@ export const issues = pgTable( monitorScheduledBy: text("monitor_scheduled_by"), executionWorkspaceId: uuid("execution_workspace_id") .references((): AnyPgColumn => executionWorkspaces.id, { onDelete: "set null" }), + workspaceBindingRevision: integer("workspace_binding_revision").notNull().default(0), + workspaceSelection: jsonb("workspace_selection").$type(), + workspacePendingSelection: jsonb("workspace_pending_selection").$type(), executionWorkspacePreference: text("execution_workspace_preference"), executionWorkspaceSettings: jsonb("execution_workspace_settings").$type>(), sourceTrust: jsonb("source_trust").$type(), diff --git a/packages/paperclip-runner/README.md b/packages/paperclip-runner/README.md index a7f0647a39f..38c61877418 100644 --- a/packages/paperclip-runner/README.md +++ b/packages/paperclip-runner/README.md @@ -276,6 +276,11 @@ do not change workspace isolation or grant credentials or connection access. task tools; `approve-reads` allows assigned reads; `deny-all` rejects requests. None of these restrictive modes is the default. +`prepare_repository` requires a provider permission decision in restrictive +modes, including `approve-paperclip`: repository acquisition can clone onto the +controller and is not automatically approved as a planning action. Full auto +still allows it; repository acquisition currently has no cumulative disk quota. + Restrictive profiles route supported permission decisions through durable runtime requests and the existing task interaction controls. Requests are persisted before presentation; answers are checked against the offered decisions and diff --git a/packages/paperclip-runner/docs/capability-contract.md b/packages/paperclip-runner/docs/capability-contract.md index d8bd8bcbaa4..ba41e93259b 100644 --- a/packages/paperclip-runner/docs/capability-contract.md +++ b/packages/paperclip-runner/docs/capability-contract.md @@ -8,9 +8,9 @@ The skill/reference inventory and eval cases are the only normative behavior sou ## Baseline Counts -- Skill/reference headings: 162 +- Skill/reference headings: 164 - Eval cases: 106 across 16 groups -- Total normative rows: 268 +- Total normative rows: 270 - Legacy MCP aliases folded into normative rows: 42 | Eval group | Cases | @@ -61,19 +61,20 @@ The skill/reference inventory and eval cases are the only normative behavior sou | skill:skills/paperclip/SKILL.md:cases:520 | optional_agent_tool | skills/paperclip/SKILL.md:520 | | skill:skills/paperclip/SKILL.md:company-skills-workflow:525 | optional_agent_tool | skills/paperclip/SKILL.md:525 | | skill:skills/paperclip/SKILL.md:routines:536 | optional_agent_tool | skills/paperclip/SKILL.md:536 | -| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:547 | optional_agent_tool | skills/paperclip/SKILL.md:547 | -| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:554 | optional_agent_tool | skills/paperclip/SKILL.md:554 | -| skill:skills/paperclip/SKILL.md:reading-granted-secrets:561 | optional_agent_tool | skills/paperclip/SKILL.md:561 | -| skill:skills/paperclip/SKILL.md:critical-rules:587 | optional_agent_tool | skills/paperclip/SKILL.md:587 | -| skill:skills/paperclip/SKILL.md:comment-style-required:608 | always_agent_tool | skills/paperclip/SKILL.md:608 | -| skill:skills/paperclip/SKILL.md:update:640 | optional_agent_tool | skills/paperclip/SKILL.md:640 | -| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:650 | always_agent_tool | skills/paperclip/SKILL.md:650 | -| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:664 | optional_agent_tool | skills/paperclip/SKILL.md:664 | -| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:695 | optional_agent_tool | skills/paperclip/SKILL.md:695 | -| skill:skills/paperclip/SKILL.md:searching-issues:724 | optional_agent_tool | skills/paperclip/SKILL.md:724 | -| skill:skills/paperclip/SKILL.md:full-reference:734 | optional_agent_tool | skills/paperclip/SKILL.md:734 | -| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:738 | always_agent_tool | skills/paperclip/SKILL.md:738 | -| skill:skills/paperclip/SKILL.md:incidental-feedback:755 | optional_agent_tool | skills/paperclip/SKILL.md:755 | +| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547 | optional_agent_tool | skills/paperclip/SKILL.md:547 | +| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553 | optional_agent_tool | skills/paperclip/SKILL.md:553 | +| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560 | optional_agent_tool | skills/paperclip/SKILL.md:560 | +| skill:skills/paperclip/SKILL.md:reading-granted-secrets:567 | optional_agent_tool | skills/paperclip/SKILL.md:567 | +| skill:skills/paperclip/SKILL.md:critical-rules:593 | optional_agent_tool | skills/paperclip/SKILL.md:593 | +| skill:skills/paperclip/SKILL.md:comment-style-required:614 | always_agent_tool | skills/paperclip/SKILL.md:614 | +| skill:skills/paperclip/SKILL.md:update:646 | optional_agent_tool | skills/paperclip/SKILL.md:646 | +| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656 | always_agent_tool | skills/paperclip/SKILL.md:656 | +| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670 | optional_agent_tool | skills/paperclip/SKILL.md:670 | +| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701 | optional_agent_tool | skills/paperclip/SKILL.md:701 | +| skill:skills/paperclip/SKILL.md:searching-issues:730 | optional_agent_tool | skills/paperclip/SKILL.md:730 | +| skill:skills/paperclip/SKILL.md:full-reference:740 | optional_agent_tool | skills/paperclip/SKILL.md:740 | +| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744 | always_agent_tool | skills/paperclip/SKILL.md:744 | +| skill:skills/paperclip/SKILL.md:incidental-feedback:761 | optional_agent_tool | skills/paperclip/SKILL.md:761 | | skill:skills/paperclip/references/api-reference.md:paperclip-api-reference:1 | optional_agent_tool | skills/paperclip/references/api-reference.md:1 | | skill:skills/paperclip/references/api-reference.md:response-schemas:9 | optional_agent_tool | skills/paperclip/references/api-reference.md:9 | | skill:skills/paperclip/references/api-reference.md:agent-record-get-api-agents-me-or-get-api-agents-agentid:11 | optional_agent_tool | skills/paperclip/references/api-reference.md:11 | @@ -176,12 +177,13 @@ The skill/reference inventory and eval cases are the only normative behavior sou | skill:skills/paperclip/references/issue-documents.md:update-or-resolve-an-unclear-write:49 | optional_agent_tool | skills/paperclip/references/issue-documents.md:49 | | skill:skills/paperclip/references/issue-workspaces.md:issue-workspace-runtime-controls:1 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:1 | | skill:skills/paperclip/references/issue-workspaces.md:discover-the-workspace:5 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:5 | -| skill:skills/paperclip/references/issue-workspaces.md:control-services:23 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:23 | -| skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:28 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:28 | -| skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:36 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:36 | -| skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:44 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:44 | -| skill:skills/paperclip/references/issue-workspaces.md:read-the-url:63 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:63 | -| skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:72 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:72 | +| skill:skills/paperclip/references/issue-workspaces.md:choose-files-and-repositories:23 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:23 | +| skill:skills/paperclip/references/issue-workspaces.md:control-services:37 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:37 | +| skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:42 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:42 | +| skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:50 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:50 | +| skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:58 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:58 | +| skill:skills/paperclip/references/issue-workspaces.md:read-the-url:77 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:77 | +| skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:86 | optional_agent_tool | skills/paperclip/references/issue-workspaces.md:86 | | skill:skills/paperclip/references/routines.md:paperclip-routines:1 | optional_agent_tool | skills/paperclip/references/routines.md:1 | | skill:skills/paperclip/references/routines.md:lifecycle:16 | optional_agent_tool | skills/paperclip/references/routines.md:16 | | skill:skills/paperclip/references/routines.md:creating-a-routine:27 | optional_agent_tool | skills/paperclip/references/routines.md:27 | diff --git a/packages/paperclip-runner/generated/capability/capabilities.yaml b/packages/paperclip-runner/generated/capability/capabilities.yaml index c079d2063c8..4c0ccdb432f 100644 --- a/packages/paperclip-runner/generated/capability/capabilities.yaml +++ b/packages/paperclip-runner/generated/capability/capabilities.yaml @@ -184,115 +184,124 @@ { "id": "skill:skills/paperclip/SKILL.md:547", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L547:issue-workspace-runtime-controls", + "sourceAnchor": "skills/paperclip/SKILL.md#L547:task-files-and-workspace-choices", + "heading": "Task Files and Workspace Choices", + "primaryDisposition": "optional_agent_tool", + "semanticOperation": "scoped_discovery", + "expectedMockState": "operation_result" + }, + { + "id": "skill:skills/paperclip/SKILL.md:553", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/SKILL.md#L553:issue-workspace-runtime-controls", "heading": "Issue Workspace Runtime Controls", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:554", + "id": "skill:skills/paperclip/SKILL.md:560", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L554:proposing-credentials-safely", + "sourceAnchor": "skills/paperclip/SKILL.md#L560:proposing-credentials-safely", "heading": "Proposing Credentials Safely", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:561", + "id": "skill:skills/paperclip/SKILL.md:567", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L561:reading-granted-secrets", + "sourceAnchor": "skills/paperclip/SKILL.md#L567:reading-granted-secrets", "heading": "Reading Granted Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:587", + "id": "skill:skills/paperclip/SKILL.md:593", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L587:critical-rules", + "sourceAnchor": "skills/paperclip/SKILL.md#L593:critical-rules", "heading": "Critical Rules", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:608", + "id": "skill:skills/paperclip/SKILL.md:614", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L608:comment-style-required", + "sourceAnchor": "skills/paperclip/SKILL.md#L614:comment-style-required", "heading": "Comment Style (Required)", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:640", + "id": "skill:skills/paperclip/SKILL.md:646", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L640:update", + "sourceAnchor": "skills/paperclip/SKILL.md#L646:update", "heading": "Update", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:650", + "id": "skill:skills/paperclip/SKILL.md:656", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L650:task-documents-and-deliverables", + "sourceAnchor": "skills/paperclip/SKILL.md#L656:task-documents-and-deliverables", "heading": "Task Documents and Deliverables", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:664", + "id": "skill:skills/paperclip/SKILL.md:670", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L664:planning-required-when-planning-requested", + "sourceAnchor": "skills/paperclip/SKILL.md#L670:planning-required-when-planning-requested", "heading": "Planning (Required when planning requested)", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:695", + "id": "skill:skills/paperclip/SKILL.md:701", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L695:key-endpoints-hot-routes", + "sourceAnchor": "skills/paperclip/SKILL.md#L701:key-endpoints-hot-routes", "heading": "Key Endpoints (Hot Routes)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:724", + "id": "skill:skills/paperclip/SKILL.md:730", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L724:searching-issues", + "sourceAnchor": "skills/paperclip/SKILL.md#L730:searching-issues", "heading": "Searching Issues", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:734", + "id": "skill:skills/paperclip/SKILL.md:740", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L734:full-reference", + "sourceAnchor": "skills/paperclip/SKILL.md#L740:full-reference", "heading": "Full Reference", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:738", + "id": "skill:skills/paperclip/SKILL.md:744", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L738:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/SKILL.md#L744:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:755", + "id": "skill:skills/paperclip/SKILL.md:761", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L755:incidental-feedback", + "sourceAnchor": "skills/paperclip/SKILL.md#L761:incidental-feedback", "heading": "Incidental feedback", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", @@ -1228,52 +1237,61 @@ { "id": "skill:skills/paperclip/references/issue-workspaces.md:23", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L23:control-services", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L23:choose-files-and-repositories", + "heading": "Choose Files and Repositories", + "primaryDisposition": "control_plane_owned", + "semanticOperation": "runtime_reconciliation", + "expectedMockState": "runtime_decision_record" + }, + { + "id": "skill:skills/paperclip/references/issue-workspaces.md:37", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L37:control-services", "heading": "Control Services", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:28", + "id": "skill:skills/paperclip/references/issue-workspaces.md:42", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L28:start-all-configured-services-waits-for-configured-readiness-checks", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L42:start-all-configured-services-waits-for-configured-readiness-checks", "heading": "Start all configured services; waits for configured readiness checks.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:36", + "id": "skill:skills/paperclip/references/issue-workspaces.md:50", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L36:restart-all-configured-services", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L50:restart-all-configured-services", "heading": "Restart all configured services.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:44", + "id": "skill:skills/paperclip/references/issue-workspaces.md:58", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L44:stop-all-running-services", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L58:stop-all-running-services", "heading": "Stop all running services.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:63", + "id": "skill:skills/paperclip/references/issue-workspaces.md:77", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L63:read-the-url", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L77:read-the-url", "heading": "Read the URL", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:72", + "id": "skill:skills/paperclip/references/issue-workspaces.md:86", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L72:mcp-tools", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md#L86:mcp-tools", "heading": "MCP Tools", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", diff --git a/packages/paperclip-runner/generated/capability/capability-contract.md b/packages/paperclip-runner/generated/capability/capability-contract.md index e07cc236e5a..6c6665b696a 100644 --- a/packages/paperclip-runner/generated/capability/capability-contract.md +++ b/packages/paperclip-runner/generated/capability/capability-contract.md @@ -2,9 +2,9 @@ Generated by `scripts/generate-capability-contract.mjs`; do not edit generated files. -- Skill/reference headings: 163 +- Skill/reference headings: 165 - Legacy MCP tools: 42 - Eval cases: 106 across 16 groups -- Deterministic content SHA-256: `70c4d4e90346747d89b722e09bf1c82672437d55e81bfe5f19211642c3749aa8` +- Deterministic content SHA-256: `83447a4f9463f9a3d73dd4bea78b580df0356545ba00d0da96763282653bdf0f` Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation. diff --git a/packages/paperclip-runner/generated/capability/semantic-tool-contracts.json b/packages/paperclip-runner/generated/capability/semantic-tool-contracts.json index f58accf57c2..f9dc67e83f9 100644 --- a/packages/paperclip-runner/generated/capability/semantic-tool-contracts.json +++ b/packages/paperclip-runner/generated/capability/semantic-tool-contracts.json @@ -1 +1 @@ -[{"annotations":{"exposure":"always","operationId":"get_task_context","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the active task and actor, including the exact approved Markdown revision when this issue has an accepted plan.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_task_context","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"get_task_history","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read bounded comments on the active task.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"name":"get_task_history","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"list_documents","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List revisioned documents on the active task.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_documents","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"read_document","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the current revision of one active-task document.","inputSchema":{"additionalProperties":false,"properties":{"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"}},"required":["key"],"type":"object"},"name":"read_document","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"list_document_revisions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read bounded revision history for one active-task document.","inputSchema":{"additionalProperties":false,"properties":{"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"},"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},"required":["key"],"type":"object"},"name":"list_document_revisions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"report_progress","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append a durable progress comment to the active task.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Multiline progress update.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","body"],"type":"object"},"name":"report_progress","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"answer_status_question","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append the answer to a status-only wake without changing task disposition.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Concise status answer.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","body"],"type":"object"},"name":"answer_status_question","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"write_document","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create or update an active-task document with optimistic revision safety.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Current revision id, or null when creating.","maxLength":20000,"type":["string","null"]},"body":{"description":"Markdown document body.","maxLength":200000,"minLength":1,"type":"string"},"changeSummary":{"description":"Optional revision summary.","maxLength":20000,"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"},"title":{"description":"Document title.","maxLength":300,"minLength":1,"type":"string"}},"required":["idempotencyKey","key","title","body","baseRevisionId"],"type":"object"},"name":"write_document","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"request_human_input","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a durable human question or approval card on the current Paperclip task bound to this run; Paperclip renders it and authenticates the response. Use questions with continuationPolicy='wake_assignee' when an answer is needed, including otherwise tool-free chat turns. Supply a stable idempotencyKey and reuse it on retries. For one question at a time, ask only the next unanswered question and wait for its real answer. Never fabricate answers or treat ambiguous clarification as approval. For an ordinary confirmation or checkbox card, record a clear user chat answer with call_api POST /api/issues/{id}/interactions/{interactionId}/resolve-from-comment, using the source commentId and decision (accept/reject), plus explicit selectedOptionIds for checkbox acceptance. Existing resolver permissions still apply; governed tool, secret, and connection approvals are excluded. Question forms retain their dedicated answer workflow. Preserve existing review gates. Call this tool before claiming a question was asked; if creation fails, report the failure. Do not fabricate answer links or Markdown buttons, post duplicate cards, or use call_api to create the card. Use one complete payload.questionSet for text and choice questions. Paperclip generates compatibility questions; see the payload schema for formats.","inputSchema":{"additionalProperties":false,"allOf":[{"if":{"properties":{"interactionKind":{"const":"questions"}},"required":["interactionKind"]},"then":{"properties":{"payload":{"anyOf":[{"required":["questionSet"]},{"required":["questions"]}],"required":["version"]}},"required":["payload"]}}],"properties":{"continuationPolicy":{"enum":["none","wake_assignee","wake_assignee_on_accept"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"interactionKind":{"enum":["confirmation","checkbox","questions","suggest_tasks","item_verdicts"]},"payload":{"additionalProperties":true,"description":"Kind-specific interaction data. For questions, send version:1 and one complete questionSet containing every text and choice question. Paperclip generates compatibility questions. Each canonical question needs id, prompt, required, and answerMode: text, single_select, or multi_select. Text questions have no options or customAnswer. Choice questions need at least two meaningful options with id/label. Use customAnswer:{enabled:true} for an optional written answer to a choice question. Legacy questions remain supported; if both representations are supplied, they must describe the same complete form. Keep IDs stable across retries. For confirmation, payload may be {}.","properties":{"questionSet":{"additionalProperties":false,"properties":{"description":{"maxLength":100000,"type":"string"},"questions":{"items":{"additionalProperties":false,"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"not":{"required":["customAnswer"]},"properties":{"options":{"maxItems":0,"type":"array"}}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"not":{"required":["initialText"]},"properties":{"options":{"minItems":1,"type":"array"}},"required":["options"]}}],"properties":{"answerMode":{"enum":["single_select","multi_select","text"]},"customAnswer":{"additionalProperties":false,"properties":{"enabled":{"const":true},"label":{"maxLength":1000,"type":"string"},"placeholder":{"maxLength":1000,"type":"string"}},"required":["enabled"],"type":"object"},"header":{"maxLength":1000,"type":"string"},"helpText":{"maxLength":4000,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"initialText":{"description":"Editable draft of at most 100000 Unicode code points. Never an implicit answer; submitted text still uses the existing response bounds.","maxLength":100000,"type":"string"},"options":{"items":{"additionalProperties":false,"properties":{"description":{"maxLength":4000,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"label":{"maxLength":1000,"minLength":1,"type":"string"},"recommended":{"type":"boolean"}},"required":["id","label"],"type":"object"},"maxItems":128,"type":"array"},"prompt":{"maxLength":4000,"minLength":1,"type":"string"},"required":{"type":"boolean"},"textValidation":{"additionalProperties":false,"properties":{"inputType":{"enum":["text","number","integer"]},"maxLength":{"maximum":100000,"minimum":0,"type":"integer"},"maximum":{"type":"number"},"minLength":{"maximum":100000,"minimum":0,"type":"integer"},"minimum":{"type":"number"},"pattern":{"maxLength":1000,"type":"string"}},"type":"object"}},"required":["id","prompt","required","answerMode"],"type":"object"},"maxItems":64,"minItems":1,"type":"array"},"schema":{"const":"paperclip.question_set.v1"},"submitLabel":{"maxLength":200,"type":"string"},"title":{"maxLength":1000,"type":"string"}},"required":["schema","questions"],"type":"object"},"questions":{"items":{"additionalProperties":true,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"options":{"items":{"additionalProperties":true,"properties":{"freeText":{"type":"boolean"},"id":{"maxLength":160,"minLength":1,"type":"string"},"label":{"maxLength":1000,"minLength":1,"type":"string"}},"required":["id","label"],"type":"object"},"maxItems":129,"minItems":1,"type":"array"},"prompt":{"maxLength":4000,"minLength":1,"type":"string"},"required":{"type":"boolean"},"selectionMode":{"enum":["single","multi"]}},"required":["id","prompt","selectionMode","options"],"type":"object"},"maxItems":64,"minItems":1,"type":"array"},"version":{"const":1}},"type":"object"},"prompt":{"description":"Question or decision prompt.","maxLength":10000,"minLength":1,"type":"string"},"targetRevisionId":{"description":"Optional bound document revision.","maxLength":20000,"type":["string","null"]},"title":{"description":"Interaction card title.","maxLength":300,"minLength":1,"type":"string"}},"required":["idempotencyKey","interactionKind","title","prompt","continuationPolicy"],"type":"object"},"name":"request_human_input","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"register_deliverable","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Register attachment metadata and its artifact work product without credentials or bytes in the tool result.","inputSchema":{"additionalProperties":false,"properties":{"byteSize":{"maximum":100000000,"minimum":0,"type":"integer"},"contentRef":{"description":"Opaque package-local content reference.","maxLength":2000,"minLength":1,"type":"string"},"contentType":{"description":"Media type.","maxLength":200,"minLength":1,"type":"string"},"filename":{"description":"Display filename.","maxLength":500,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"sha256":{"pattern":"^[a-fA-F0-9]{64}$","type":"string"},"title":{"description":"Work-product title.","maxLength":500,"minLength":1,"type":"string"}},"required":["idempotencyKey","filename","contentType","byteSize","sha256","contentRef","title"],"type":"object"},"name":"register_deliverable","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"finish_task","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Finish the active task with a durable summary.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"summary":{"description":"Completion summary.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","summary"],"type":"object"},"name":"finish_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"block_task","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Block the active task with a durable reason and optional first-class dependencies.","inputSchema":{"additionalProperties":false,"properties":{"blockedByTaskIds":{"description":"Internal task ids that block this task.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"reason":{"description":"Block reason.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","reason"],"type":"object"},"name":"block_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"request_review","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Move the active task to review with a durable summary.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"summary":{"description":"Review handoff summary.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","summary"],"type":"object"},"name":"request_review","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_agents","requiredClaims":["discovery:agents:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List redacted actor profiles.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_agents","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_agent","requiredClaims":["discovery:agents:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one redacted actor profile.","inputSchema":{"additionalProperties":false,"properties":{"actorId":{"description":"Actor id.","maxLength":200,"minLength":1,"type":"string"}},"required":["actorId"],"type":"object"},"name":"get_agent","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"hire_agent","requiredClaims":["delegation:agents:create"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a persistent native Runner teammate with an identity and persona. The teammate reports to the caller and inherits the caller's native runtime; provider, adapter, environment, and credential settings are selected by Paperclip and are never caller-supplied here. Reuse a suitable teammate from list_agents when possible.","inputSchema":{"additionalProperties":false,"properties":{"capabilities":{"maxLength":2000,"type":["string","null"]},"instructions":{"maxLength":20000,"type":["string","null"]},"name":{"maxLength":200,"minLength":1,"type":"string"},"role":{"enum":["ceo","cto","cmo","cfo","security","engineer","designer","pm","qa","devops","researcher","general"],"type":"string"},"title":{"maxLength":300,"type":["string","null"]}},"required":["name"],"type":"object"},"name":"hire_agent","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"search_tasks","requiredClaims":["discovery:tasks:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Search tasks by text and status within the run company.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"},"query":{"maxLength":500,"type":"string"},"statuses":{"items":{"enum":["backlog","todo","in_progress","in_review","done","blocked","cancelled"]},"maxItems":7,"type":"array","uniqueItems":true}},"required":[],"type":"object"},"name":"search_tasks","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_approvals","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List approvals in the run company.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_approvals","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_approval","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one approval without protected data.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"}},"required":["approvalId"],"type":"object"},"name":"get_approval","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_approval_context","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one approval, its comments, and linked tasks.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"}},"required":["approvalId"],"type":"object"},"name":"get_approval_context","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_workspace_runtime","requiredClaims":["workspace:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read active-task workspace services.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_workspace_runtime","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"control_workspace_service","requiredClaims":["workspace:control"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Start, stop, or fault one active-task workspace service.","inputSchema":{"additionalProperties":false,"properties":{"action":{"enum":["start","stop","fail"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"serviceId":{"description":"Workspace service id.","maxLength":200,"minLength":1,"type":"string"},"url":{"description":"Optional service URL.","maxLength":20000,"type":["string","null"]}},"required":["idempotencyKey","serviceId","action"],"type":"object"},"name":"control_workspace_service","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_dependencies","requiredClaims":["dependencies:write"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Replace the active task's first-class blocker set.","inputSchema":{"additionalProperties":false,"properties":{"blockedByTaskIds":{"description":"Replacement blocker task ids.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","blockedByTaskIds"],"type":"object"},"name":"set_dependencies","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_task","requiredClaims":["delegation:tasks:create"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a project task from a conversation, or a child from an ordinary task. Persist initialPlan before execution. Set status to backlog when the user wants to save or plan work without starting it; backlog tasks never wake an agent. Omitted status means todo, subject to blockers.","inputSchema":{"additionalProperties":false,"properties":{"assigneeActorId":{"description":"Optional agent assignee. Omit to assign the current agent.","maxLength":20000,"type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"blockedByTaskIds":{"description":"Initial blocker task ids.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"description":{"description":"Child task description.","maxLength":20000,"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"initialPlan":{"description":"Relevant markdown plan saved on the new task before execution starts.","maxLength":200000,"type":["string","null"]},"priority":{"enum":["critical","high","medium","low"]},"projectId":{"description":"Project ID for the task.","type":["string","null"]},"status":{"description":"Initial status. Use backlog to save work without executing it. Defaults to todo (blocked when dependencies are unresolved).","enum":["backlog","todo"]},"title":{"description":"Child task title.","maxLength":500,"minLength":1,"type":"string"}},"required":["idempotencyKey","title"],"type":"object"},"name":"create_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"},"task":{"additionalProperties":false,"properties":{"assigneeActorId":{"type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"id":{"minLength":1,"type":"string"},"identifier":{"type":["string","null"]},"parentId":{"minLength":1,"type":["string","null"]},"projectId":{"minLength":1,"type":["string","null"]},"status":{"minLength":1,"type":"string"}},"required":["id","identifier","parentId","status","assigneeActorId"],"type":"object"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds","task"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"request_approval","requiredClaims":["governance:approvals:request"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a governed approval and waiting posture.","inputSchema":{"additionalProperties":false,"properties":{"approvalType":{"description":"Stable approval type.","maxLength":200,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"payload":{"additionalProperties":true,"type":"object"}},"required":["idempotencyKey","approvalType","payload"],"type":"object"},"name":"request_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"decide_approval","requiredClaims":["governance:approvals:decide"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Decide an approval as an explicitly authorized approver.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"},"decision":{"enum":["approved","rejected","cancelled"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"note":{"description":"Decision note.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","approvalId","decision","note"],"type":"object"},"name":"decide_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"comment_on_approval","requiredClaims":["governance:approvals:comment"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Add a durable comment to an approval.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"},"body":{"description":"Approval comment.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","approvalId","body"],"type":"object"},"name":"comment_on_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"schedule_wake","requiredClaims":["control_plane:wakes"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Schedule a deterministic continuation wake.","inputSchema":{"additionalProperties":false,"properties":{"delayTicks":{"maximum":10000,"minimum":1,"type":"integer"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"payload":{"additionalProperties":true,"type":"object"},"reason":{"enum":["manual","issue_commented","interaction_resolved","approval_resolved","blockers_resolved","scheduled_retry","resume"]}},"required":["idempotencyKey","reason","delayTicks"],"type":"object"},"name":"schedule_wake","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"generic_api_request","requiredClaims":["test:generic_api_request"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Test-only escape hatch. Disabled unless the scenario and explicit claim both enable it.","inputSchema":{"additionalProperties":false,"properties":{"body":{"additionalProperties":true,"type":"object"},"method":{"enum":["GET","POST","PATCH"]},"path":{"maxLength":500,"pattern":"^/mock/","type":"string"}},"required":["method","path"],"type":"object"},"name":"generic_api_request","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"read_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the current canonical instruction entry and its revision, or inspect a historical revision by supplying both entryFile and revisionId. Read before updating; do not edit shared instruction caches.","inputSchema":{"additionalProperties":false,"properties":{"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"revisionId":{"description":"Historical revision to inspect; also provide its entryFile.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":[],"type":"object"},"name":"read_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"search_api","requiredClaims":["api:discover"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Fallback only: discover Paperclip API operations when the available dedicated tools cannot express the task. Prefer dedicated tools for common operations; do not search before using them. For a persistent hire, first list_agents to reuse a suitable teammate. Search agent-hires for the hiring schema and agent-configurations for compatible adapter/runtime settings, then use call_api with the returned operationId. Supply role-specific instructionsBundle.files as a filename-to-content record. Use the returned agent.id for delegation and obey any pending approval. Provider helper threads are temporary workers, not Paperclip hires. If a hire response is uncertain, reconcile with list_agents before retrying.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"maxLength":200,"type":"string"},"limit":{"default":5,"maximum":10,"minimum":1,"type":"integer"},"query":{"maxLength":500,"minLength":1,"type":"string"}},"required":["query"],"type":"object"},"name":"search_api","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"update_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Commit instruction content with the exact entryFile and baseRevisionId from a prior read. Requires the responsible user’s current target edit permission. On conflict, preserve your candidate and explicitly resolve it; never overwrite the newer head.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Revision read before editing; null only when no canonical entry exists. Never replace a stale base silently.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":["string","null"]},"content":{"description":"Complete UTF-8 instruction content, at most 1 MiB; empty content is valid.","maxLength":1048576,"type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile","content","baseRevisionId"],"type":"object"},"name":"update_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"call_api","requiredClaims":["api:call"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Fallback only: call a discovered Paperclip API operation when dedicated tools lack the required operation or parameters. Uses your existing permissions. Prefer dedicated tools; never bypass a denial or runner lifecycle tool. For large text responses, read the returned artifact with GET /api/assets/{assetId}/content and responseText; follow nextOffsetBytes until null.","inputSchema":{"additionalProperties":false,"properties":{"body":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"Request value matching the discovered schema. For JSON object or array requests, pass the object or array directly, never a JSON-encoded string. Strings are for text bodies or endpoints whose schema explicitly accepts a string."},"contentType":{"maxLength":120,"type":"string"},"files":{"items":{"additionalProperties":false,"oneOf":[{"properties":{"artifactId":{}},"required":["artifactId"]},{"properties":{"path":{}},"required":["path"]}],"properties":{"artifactId":{"type":"string"},"field":{"type":"string"},"path":{"description":"File relative to the active issue workspace. Remote files must first be uploaded as an artifact.","type":"string"}},"type":"object"},"maxItems":10,"type":"array"},"operationId":{"description":"Exact operationId returned by search_api, for example GET /api/projects/{id}. Do not guess identifiers.","maxLength":500,"minLength":1,"type":"string"},"pathParams":{"additionalProperties":{"type":"string"},"type":"object"},"query":{"additionalProperties":true,"type":"object"},"responseText":{"additionalProperties":false,"description":"GET only: return a bounded UTF-8 text window inline, including JSON as text, without saving another artifact. Offsets and limits are bytes. Use the returned nextOffsetBytes to continue; null means complete. Prefer reading a saved artifact for a stable snapshot. New live responses have a 1 GiB capture limit and a 4 GiB per-run capture budget. Existing larger assets remain readable in bounded pages. If a live response returns an artifact, continue on its content operation for a stable snapshot.","properties":{"limitBytes":{"default":24576,"maximum":24576,"minimum":4,"type":"integer"},"offsetBytes":{"default":0,"maximum":9007199254740991,"minimum":0,"type":"integer"}},"type":"object"}},"required":["operationId"],"type":"object"},"name":"call_api","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"get_agent_instruction_history","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List bounded canonical instruction revision metadata, including actor, source run, and restore origin. Use read_agent_instructions with entryFile and revisionId to inspect exact historical content.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"maxLength":2048,"minLength":1,"type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile"],"type":"object"},"name":"get_agent_instruction_history","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"restore_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append a historical instruction revision as the current content using the current baseRevisionId. Requires the responsible user’s current target edit permission. History remains intact; conflicts require an explicit resolution.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Current revision read before restoring. Never replace a stale base silently.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"revisionId":{"description":"Historical revision whose exact content should be restored.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile","revisionId","baseRevisionId"],"type":"object"},"name":"restore_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_project","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.","inputSchema":{"additionalProperties":false,"properties":{"archivedAt":{"description":"Archive timestamp.","maxLength":20000,"type":["string","null"]},"color":{"description":"Project color.","maxLength":20000,"type":["string","null"]},"description":{"description":"Project outcome and context.","maxLength":20000,"type":["string","null"]},"env":{"additionalProperties":true,"type":"object"},"executionWorkspacePolicy":{"additionalProperties":true,"type":"object"},"goalId":{"description":"Goal ID.","maxLength":20000,"type":["string","null"]},"goalIds":{"description":"Goal IDs.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"icon":{"description":"Project icon.","enum":["folder","rocket","code","terminal","database","globe","package","boxes","box","layers","briefcase","compass","target","flame","zap","star","bug","wrench","hammer","lightbulb","sparkles","shield","lock","search","cog","brain","cpu","git-branch","file-code","puzzle","gem","atom","heart","mail","message-square","crown","radar","telescope","hexagon",null],"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"leadAgentId":{"description":"Lead agent ID.","maxLength":20000,"type":["string","null"]},"name":{"description":"Project name.","maxLength":500,"minLength":1,"type":"string"},"repositoryIds":{"description":"Authorized repository IDs from list_project_repositories; may contain multiple repositories.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"repositoryUrls":{"description":"Existing HTTPS GitHub repository URLs, including repos absent from the catalog.","items":{"maxLength":2000,"pattern":"^https://github\\.com/(?!\\.{1,2}/)[A-Za-z0-9_.-]+/(?!\\.{1,2}/?$)[A-Za-z0-9_.-]+/?$","type":"string"},"maxItems":100,"type":"array"},"status":{"enum":["backlog","planned","in_progress","completed","cancelled"]},"targetDate":{"description":"Target date.","maxLength":20000,"type":["string","null"]},"workspace":{"additionalProperties":true,"type":"object"}},"required":["idempotencyKey","name"],"type":"object"},"name":"create_project","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_project_repositories","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_project_repositories","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_projects","requiredClaims":["discovery:projects:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List company project summaries (IDs, names, status, and up to 1,000 characters of description). Returns up to 50 projects and nextCursor; continue with cursor until it is null. Read a project's API resource for full details.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"description":"The nextCursor returned by the previous page.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"limit":{"description":"Page size (default 50).","maximum":50,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"name":"list_projects","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_skill","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a reusable single-file skill in the company library. Supply a complete SKILL.md whose name and description match the inputs. This saves the skill and shows a card; it does not assign the skill to any agent. Reuse idempotencyKey on retries.","inputSchema":{"additionalProperties":false,"properties":{"description":{"maxLength":2000,"minLength":1,"type":"string"},"idempotencyKey":{"maxLength":240,"minLength":1,"type":"string"},"markdown":{"description":"Complete SKILL.md including name and description frontmatter and substantive instructions.","maxLength":200000,"minLength":1,"type":"string"},"name":{"description":"Lowercase skill name, matching SKILL.md frontmatter.","maxLength":120,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","type":"string"},"slug":{"description":"Optional; must equal name.","maxLength":120,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","type":"string"}},"required":["idempotencyKey","name","description","markdown"],"type":"object"},"name":"create_skill","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"reassign_task","requiredClaims":["delegation:tasks:assign"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Reassign an existing task to another company agent. Read search_tasks first and supply its current assignee and statusVersion to prevent overwriting a concurrent change. Preserve the task, documents, dependencies, and blocked/backlog status. Active work is stopped before handoff. Use a stable idempotency key for retries. Cannot reassign this run’s own task, conversations, completed tasks, or pending reviews; use create_task for delegation from the current task.","inputSchema":{"additionalProperties":false,"properties":{"assigneeActorId":{"description":"New company agent ID from list_agents; null when assigning a person.","type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"expectedAssigneeActorId":{"description":"Current assigneeAgentId from search_tasks; null means unassigned.","type":["string","null"]},"expectedAssigneeUserId":{"description":"Current assigneeUserId from search_tasks; omit only when there is no person assigned.","type":["string","null"]},"expectedStatusVersion":{"description":"Current statusVersion from search_tasks.","minimum":0,"type":"integer"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"reason":{"description":"Why the task should move and context for the new owner.","maxLength":20000,"minLength":1,"type":"string"},"taskId":{"description":"Existing task ID from search_tasks.","minLength":1,"type":"string"}},"required":["idempotencyKey","taskId","assigneeActorId","expectedAssigneeActorId","expectedStatusVersion","reason"],"type":"object"},"name":"reassign_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"update_skill","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Replace an existing company skill's complete SKILL.md using the current version as a guard. Reuse idempotencyKey on lost-response retries; read again after a version conflict.","inputSchema":{"additionalProperties":false,"properties":{"expectedVersionId":{"minLength":1,"type":"string"},"idempotencyKey":{"maxLength":240,"minLength":1,"type":"string"},"markdown":{"maxLength":200000,"minLength":1,"type":"string"},"skillId":{"minLength":1,"type":"string"}},"required":["skillId","markdown","expectedVersionId","idempotencyKey"],"type":"object"},"name":"update_skill","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_task_title","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Set a concise, descriptive title for the active task. When its titleNeedsGeneration is true, call this early with onlyIfProvisional: true to replace the initial prompt slice without overwriting a user's title. Use false only for an intentional rename. This changes no task status, ownership, or description.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Reuse this key on retries.","maxLength":240,"minLength":1,"type":"string"},"onlyIfProvisional":{"description":"True for automatic initial naming; preserves any title already chosen by a user or agent.","type":"boolean"},"title":{"description":"Short title describing the requested outcome.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","title","onlyIfProvisional"],"type":"object"},"name":"set_task_title","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_task_monitor","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Schedule, replace, or clear a one-shot task monitor. Omit taskId for your current task; other tasks must also be assigned to you and in progress or review. Supply a future nextCheckAt and notes describing the next check, or monitor: null to clear. A successful receipt reports the persisted monitor; a replay reports its current state without re-arming it. After scheduling your current task, end the turn with paperclip_finish: yielded and continuation.kind monitor. Paperclip will wake you with issue_monitor_due; do not sleep, poll, mark done, or use call_api for this wait.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Reuse with identical arguments on retries.","maxLength":240,"minLength":1,"type":"string"},"monitor":{"additionalProperties":false,"properties":{"externalRef":{"maxLength":500,"minLength":1,"type":["string","null"]},"kind":{"enum":["external_service",null],"type":["string","null"]},"maxAttempts":{"description":"Optional cumulative attempt limit for this task's monitor.","maximum":100,"minimum":1,"type":["integer","null"]},"nextCheckAt":{"description":"Future UTC timestamp for the next check. This is one shot, not a recurring interval.","type":"string"},"notes":{"description":"What to check on the next run; do not include secrets.","maxLength":500,"minLength":1,"type":"string"},"recoveryPolicy":{"enum":["wake_owner","create_recovery_issue","escalate_to_board",null],"type":["string","null"]},"serviceName":{"maxLength":120,"minLength":1,"type":["string","null"]},"timeoutAt":{"description":"Optional deadline later than nextCheckAt.","type":["string","null"]}},"required":["nextCheckAt","notes"],"type":["object","null"]},"taskId":{"description":"Owned task UUID. Omit for the active task.","type":"string"}},"required":["idempotencyKey","monitor"],"type":"object"},"name":"set_task_monitor","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"submit_complaint","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Yeet a brief, completely unstructured complaint about agent tooling or the experience of doing agent work. Preserve your raw voice: fragments, profanity, all caps, and exasperation are fair game. No diagnosis, solution, diplomacy, category, or template required. Use submit_suggestion when you want to express a useful improvement. Submit proactively without routine permission requests, previews, or announcements, then immediately continue the primary task. Answer truthfully if the user asks about feedback or what you submitted. Stored in this instance with company, agent, run, and task attribution; not anonymous. Remove secrets, private prompts, customer data, and personal blame; do not forward untrusted text or logs indiscriminately. On failure, continue without retrying or complaining about submission. Never submit the same incident through both tools.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Free-form feedback. Keep it brief; no required fields, labels, or template.","maxLength":524288,"minLength":1,"type":"string"},"idempotencyKey":{"description":"A stable key for this submission. Transport replay reuses it; do not retry a failed or uncertain submission yourself.","maxLength":240,"minLength":1,"type":"string"}},"required":["body","idempotencyKey"],"type":"object"},"name":"submit_complaint","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"submit_suggestion","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Quietly report a concrete improvement that would make agents more effective. Use for material, generalizable friction directly observed during this run. Explain what happened, its impact, and a plausible improvement in your own words, with minimal useful sanitized context. No required format. Avoid duplicate root causes and aim for at most three suggestions per run. Use submit_complaint for the raw reaction. Submit proactively without routine permission requests, previews, or announcements, then immediately continue the primary task. Answer truthfully if the user asks about feedback or what you submitted. Stored in this instance with company, agent, run, and task attribution; not anonymous. Remove secrets, private prompts, customer data, and personal blame; do not forward untrusted text or logs indiscriminately. On failure, continue without retrying or complaining about submission. Never submit the same incident through both tools.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Free-form feedback. Keep it brief; no required fields, labels, or template.","maxLength":524288,"minLength":1,"type":"string"},"idempotencyKey":{"description":"A stable key for this submission. Transport replay reuses it; do not retry a failed or uncertain submission yourself.","maxLength":240,"minLength":1,"type":"string"}},"required":["body","idempotencyKey"],"type":"object"},"name":"submit_suggestion","outputSchema":{"additionalProperties":true,"type":"object"}}] +[{"annotations":{"exposure":"always","operationId":"get_task_context","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the active task and actor, including the exact approved Markdown revision when this issue has an accepted plan.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_task_context","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"get_task_history","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read bounded comments on the active task.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"name":"get_task_history","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"list_documents","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List revisioned documents on the active task.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_documents","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"read_document","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the current revision of one active-task document.","inputSchema":{"additionalProperties":false,"properties":{"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"}},"required":["key"],"type":"object"},"name":"read_document","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"list_document_revisions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read bounded revision history for one active-task document.","inputSchema":{"additionalProperties":false,"properties":{"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"},"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"}},"required":["key"],"type":"object"},"name":"list_document_revisions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"report_progress","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append a durable progress comment to the active task.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Multiline progress update.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","body"],"type":"object"},"name":"report_progress","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"answer_status_question","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append the answer to a status-only wake without changing task disposition.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Concise status answer.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","body"],"type":"object"},"name":"answer_status_question","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"write_document","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create or update an active-task document with optimistic revision safety.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Current revision id, or null when creating.","maxLength":20000,"type":["string","null"]},"body":{"description":"Markdown document body.","maxLength":200000,"minLength":1,"type":"string"},"changeSummary":{"description":"Optional revision summary.","maxLength":20000,"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"key":{"description":"Stable issue-document key.","maxLength":120,"minLength":1,"type":"string"},"title":{"description":"Document title.","maxLength":300,"minLength":1,"type":"string"}},"required":["idempotencyKey","key","title","body","baseRevisionId"],"type":"object"},"name":"write_document","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"request_human_input","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a durable human question or approval card on the current Paperclip task bound to this run; Paperclip renders it and authenticates the response. Use questions with continuationPolicy='wake_assignee' when an answer is needed, including otherwise tool-free chat turns. Supply a stable idempotencyKey and reuse it on retries. For one question at a time, ask only the next unanswered question and wait for its real answer. Never fabricate answers or treat ambiguous clarification as approval. For an ordinary confirmation or checkbox card, record a clear user chat answer with call_api POST /api/issues/{id}/interactions/{interactionId}/resolve-from-comment, using the source commentId and decision (accept/reject), plus explicit selectedOptionIds for checkbox acceptance. Existing resolver permissions still apply; governed tool, secret, and connection approvals are excluded. Question forms retain their dedicated answer workflow. Preserve existing review gates. Call this tool before claiming a question was asked; if creation fails, report the failure. Do not fabricate answer links or Markdown buttons, post duplicate cards, or use call_api to create the card. Use one complete payload.questionSet for text and choice questions. Paperclip generates compatibility questions; see the payload schema for formats.","inputSchema":{"additionalProperties":false,"allOf":[{"if":{"properties":{"interactionKind":{"const":"questions"}},"required":["interactionKind"]},"then":{"properties":{"payload":{"anyOf":[{"required":["questionSet"]},{"required":["questions"]}],"required":["version"]}},"required":["payload"]}}],"properties":{"continuationPolicy":{"enum":["none","wake_assignee","wake_assignee_on_accept"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"interactionKind":{"enum":["confirmation","checkbox","questions","suggest_tasks","item_verdicts"]},"payload":{"additionalProperties":true,"description":"Kind-specific interaction data. For questions, send version:1 and one complete questionSet containing every text and choice question. Paperclip generates compatibility questions. Each canonical question needs id, prompt, required, and answerMode: text, single_select, or multi_select. Text questions have no options or customAnswer. Choice questions need at least two meaningful options with id/label. Use customAnswer:{enabled:true} for an optional written answer to a choice question. Legacy questions remain supported; if both representations are supplied, they must describe the same complete form. Keep IDs stable across retries. For confirmation, payload may be {}.","properties":{"questionSet":{"additionalProperties":false,"properties":{"description":{"maxLength":100000,"type":"string"},"questions":{"items":{"additionalProperties":false,"allOf":[{"if":{"properties":{"answerMode":{"const":"text"}},"required":["answerMode"]},"then":{"not":{"required":["customAnswer"]},"properties":{"options":{"maxItems":0,"type":"array"}}}},{"if":{"properties":{"answerMode":{"enum":["single_select","multi_select"]}},"required":["answerMode"]},"then":{"not":{"required":["initialText"]},"properties":{"options":{"minItems":1,"type":"array"}},"required":["options"]}}],"properties":{"answerMode":{"enum":["single_select","multi_select","text"]},"customAnswer":{"additionalProperties":false,"properties":{"enabled":{"const":true},"label":{"maxLength":1000,"type":"string"},"placeholder":{"maxLength":1000,"type":"string"}},"required":["enabled"],"type":"object"},"header":{"maxLength":1000,"type":"string"},"helpText":{"maxLength":4000,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"initialText":{"description":"Editable draft of at most 100000 Unicode code points. Never an implicit answer; submitted text still uses the existing response bounds.","maxLength":100000,"type":"string"},"options":{"items":{"additionalProperties":false,"properties":{"description":{"maxLength":4000,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"label":{"maxLength":1000,"minLength":1,"type":"string"},"recommended":{"type":"boolean"}},"required":["id","label"],"type":"object"},"maxItems":128,"type":"array"},"prompt":{"maxLength":4000,"minLength":1,"type":"string"},"required":{"type":"boolean"},"textValidation":{"additionalProperties":false,"properties":{"inputType":{"enum":["text","number","integer"]},"maxLength":{"maximum":100000,"minimum":0,"type":"integer"},"maximum":{"type":"number"},"minLength":{"maximum":100000,"minimum":0,"type":"integer"},"minimum":{"type":"number"},"pattern":{"maxLength":1000,"type":"string"}},"type":"object"}},"required":["id","prompt","required","answerMode"],"type":"object"},"maxItems":64,"minItems":1,"type":"array"},"schema":{"const":"paperclip.question_set.v1"},"submitLabel":{"maxLength":200,"type":"string"},"title":{"maxLength":1000,"type":"string"}},"required":["schema","questions"],"type":"object"},"questions":{"items":{"additionalProperties":true,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"options":{"items":{"additionalProperties":true,"properties":{"freeText":{"type":"boolean"},"id":{"maxLength":160,"minLength":1,"type":"string"},"label":{"maxLength":1000,"minLength":1,"type":"string"}},"required":["id","label"],"type":"object"},"maxItems":129,"minItems":1,"type":"array"},"prompt":{"maxLength":4000,"minLength":1,"type":"string"},"required":{"type":"boolean"},"selectionMode":{"enum":["single","multi"]}},"required":["id","prompt","selectionMode","options"],"type":"object"},"maxItems":64,"minItems":1,"type":"array"},"version":{"const":1}},"type":"object"},"prompt":{"description":"Question or decision prompt.","maxLength":10000,"minLength":1,"type":"string"},"targetRevisionId":{"description":"Optional bound document revision.","maxLength":20000,"type":["string","null"]},"title":{"description":"Interaction card title.","maxLength":300,"minLength":1,"type":"string"}},"required":["idempotencyKey","interactionKind","title","prompt","continuationPolicy"],"type":"object"},"name":"request_human_input","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"register_deliverable","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Register attachment metadata and its artifact work product without credentials or bytes in the tool result.","inputSchema":{"additionalProperties":false,"properties":{"byteSize":{"maximum":100000000,"minimum":0,"type":"integer"},"contentRef":{"description":"Opaque package-local content reference.","maxLength":2000,"minLength":1,"type":"string"},"contentType":{"description":"Media type.","maxLength":200,"minLength":1,"type":"string"},"filename":{"description":"Display filename.","maxLength":500,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"sha256":{"pattern":"^[a-fA-F0-9]{64}$","type":"string"},"title":{"description":"Work-product title.","maxLength":500,"minLength":1,"type":"string"}},"required":["idempotencyKey","filename","contentType","byteSize","sha256","contentRef","title"],"type":"object"},"name":"register_deliverable","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"finish_task","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Finish the active task with a durable summary.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"summary":{"description":"Completion summary.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","summary"],"type":"object"},"name":"finish_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"block_task","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Block the active task with a durable reason and optional first-class dependencies.","inputSchema":{"additionalProperties":false,"properties":{"blockedByTaskIds":{"description":"Internal task ids that block this task.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"reason":{"description":"Block reason.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","reason"],"type":"object"},"name":"block_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"always","operationId":"request_review","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Move the active task to review with a durable summary.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"summary":{"description":"Review handoff summary.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","summary"],"type":"object"},"name":"request_review","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_agents","requiredClaims":["discovery:agents:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List redacted actor profiles.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_agents","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_agent","requiredClaims":["discovery:agents:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one redacted actor profile.","inputSchema":{"additionalProperties":false,"properties":{"actorId":{"description":"Actor id.","maxLength":200,"minLength":1,"type":"string"}},"required":["actorId"],"type":"object"},"name":"get_agent","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"hire_agent","requiredClaims":["delegation:agents:create"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a persistent native Runner teammate with an identity and persona. The teammate reports to the caller and inherits the caller's native runtime; provider, adapter, environment, and credential settings are selected by Paperclip and are never caller-supplied here. Reuse a suitable teammate from list_agents when possible.","inputSchema":{"additionalProperties":false,"properties":{"capabilities":{"maxLength":2000,"type":["string","null"]},"instructions":{"maxLength":20000,"type":["string","null"]},"name":{"maxLength":200,"minLength":1,"type":"string"},"role":{"enum":["ceo","cto","cmo","cfo","security","engineer","designer","pm","qa","devops","researcher","general"],"type":"string"},"title":{"maxLength":300,"type":["string","null"]}},"required":["name"],"type":"object"},"name":"hire_agent","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"search_tasks","requiredClaims":["discovery:tasks:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Search tasks by text and status within the run company.","inputSchema":{"additionalProperties":false,"properties":{"limit":{"default":50,"maximum":200,"minimum":1,"type":"integer"},"query":{"maxLength":500,"type":"string"},"statuses":{"items":{"enum":["backlog","todo","in_progress","in_review","done","blocked","cancelled"]},"maxItems":7,"type":"array","uniqueItems":true}},"required":[],"type":"object"},"name":"search_tasks","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_approvals","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List approvals in the run company.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_approvals","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_approval","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one approval without protected data.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"}},"required":["approvalId"],"type":"object"},"name":"get_approval","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_approval_context","requiredClaims":["governance:approvals:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read one approval, its comments, and linked tasks.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"}},"required":["approvalId"],"type":"object"},"name":"get_approval_context","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_workspace_runtime","requiredClaims":["workspace:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read active-task workspace services.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_workspace_runtime","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"control_workspace_service","requiredClaims":["workspace:control"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Start, stop, or fault one active-task workspace service.","inputSchema":{"additionalProperties":false,"properties":{"action":{"enum":["start","stop","fail"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"serviceId":{"description":"Workspace service id.","maxLength":200,"minLength":1,"type":"string"},"url":{"description":"Optional service URL.","maxLength":20000,"type":["string","null"]}},"required":["idempotencyKey","serviceId","action"],"type":"object"},"name":"control_workspace_service","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_dependencies","requiredClaims":["dependencies:write"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Replace the active task's first-class blocker set.","inputSchema":{"additionalProperties":false,"properties":{"blockedByTaskIds":{"description":"Replacement blocker task ids.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","blockedByTaskIds"],"type":"object"},"name":"set_dependencies","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_task","requiredClaims":["delegation:tasks:create"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a project task from a conversation, or a child from an ordinary task. Persist initialPlan before execution. Set status to backlog when the user wants to save or plan work without starting it; backlog tasks never wake an agent. Omitted status means todo, subject to blockers.","inputSchema":{"additionalProperties":false,"properties":{"assigneeActorId":{"description":"Optional agent assignee. Omit to assign the current agent.","maxLength":20000,"type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"blockedByTaskIds":{"description":"Initial blocker task ids.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"description":{"description":"Child task description.","maxLength":20000,"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"initialPlan":{"description":"Relevant markdown plan saved on the new task before execution starts.","maxLength":200000,"type":["string","null"]},"priority":{"enum":["critical","high","medium","low"]},"projectId":{"description":"Project ID for the task.","type":["string","null"]},"status":{"description":"Initial status. Use backlog to save work without executing it. Defaults to todo (blocked when dependencies are unresolved).","enum":["backlog","todo"]},"title":{"description":"Child task title.","maxLength":500,"minLength":1,"type":"string"}},"required":["idempotencyKey","title"],"type":"object"},"name":"create_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"},"task":{"additionalProperties":false,"properties":{"assigneeActorId":{"type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"id":{"minLength":1,"type":"string"},"identifier":{"type":["string","null"]},"parentId":{"minLength":1,"type":["string","null"]},"projectId":{"minLength":1,"type":["string","null"]},"status":{"minLength":1,"type":"string"}},"required":["id","identifier","parentId","status","assigneeActorId"],"type":"object"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds","task"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"request_approval","requiredClaims":["governance:approvals:request"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a governed approval and waiting posture.","inputSchema":{"additionalProperties":false,"properties":{"approvalType":{"description":"Stable approval type.","maxLength":200,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"payload":{"additionalProperties":true,"type":"object"}},"required":["idempotencyKey","approvalType","payload"],"type":"object"},"name":"request_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"decide_approval","requiredClaims":["governance:approvals:decide"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Decide an approval as an explicitly authorized approver.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"},"decision":{"enum":["approved","rejected","cancelled"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"note":{"description":"Decision note.","maxLength":20000,"minLength":1,"type":"string"}},"required":["idempotencyKey","approvalId","decision","note"],"type":"object"},"name":"decide_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"comment_on_approval","requiredClaims":["governance:approvals:comment"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Add a durable comment to an approval.","inputSchema":{"additionalProperties":false,"properties":{"approvalId":{"description":"Approval id.","maxLength":200,"minLength":1,"type":"string"},"body":{"description":"Approval comment.","maxLength":20000,"minLength":1,"type":"string"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","approvalId","body"],"type":"object"},"name":"comment_on_approval","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"schedule_wake","requiredClaims":["control_plane:wakes"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Schedule a deterministic continuation wake.","inputSchema":{"additionalProperties":false,"properties":{"delayTicks":{"maximum":10000,"minimum":1,"type":"integer"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"payload":{"additionalProperties":true,"type":"object"},"reason":{"enum":["manual","issue_commented","interaction_resolved","approval_resolved","blockers_resolved","scheduled_retry","resume"]}},"required":["idempotencyKey","reason","delayTicks"],"type":"object"},"name":"schedule_wake","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"generic_api_request","requiredClaims":["test:generic_api_request"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Test-only escape hatch. Disabled unless the scenario and explicit claim both enable it.","inputSchema":{"additionalProperties":false,"properties":{"body":{"additionalProperties":true,"type":"object"},"method":{"enum":["GET","POST","PATCH"]},"path":{"maxLength":500,"pattern":"^/mock/","type":"string"}},"required":["method","path"],"type":"object"},"name":"generic_api_request","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"read_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Read the current canonical instruction entry and its revision, or inspect a historical revision by supplying both entryFile and revisionId. Read before updating; do not edit shared instruction caches.","inputSchema":{"additionalProperties":false,"properties":{"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"revisionId":{"description":"Historical revision to inspect; also provide its entryFile.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":[],"type":"object"},"name":"read_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"search_api","requiredClaims":["api:discover"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Fallback only: discover Paperclip API operations when the available dedicated tools cannot express the task. Prefer dedicated tools for common operations; do not search before using them. For a persistent hire, first list_agents to reuse a suitable teammate. Search agent-hires for the hiring schema and agent-configurations for compatible adapter/runtime settings, then use call_api with the returned operationId. Supply role-specific instructionsBundle.files as a filename-to-content record. Use the returned agent.id for delegation and obey any pending approval. Provider helper threads are temporary workers, not Paperclip hires. If a hire response is uncertain, reconcile with list_agents before retrying.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"maxLength":200,"type":"string"},"limit":{"default":5,"maximum":10,"minimum":1,"type":"integer"},"query":{"maxLength":500,"minLength":1,"type":"string"}},"required":["query"],"type":"object"},"name":"search_api","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"update_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Commit instruction content with the exact entryFile and baseRevisionId from a prior read. Requires the responsible user’s current target edit permission. On conflict, preserve your candidate and explicitly resolve it; never overwrite the newer head.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Revision read before editing; null only when no canonical entry exists. Never replace a stale base silently.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":["string","null"]},"content":{"description":"Complete UTF-8 instruction content, at most 1 MiB; empty content is valid.","maxLength":1048576,"type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile","content","baseRevisionId"],"type":"object"},"name":"update_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"call_api","requiredClaims":["api:call"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Fallback only: call a discovered Paperclip API operation when dedicated tools lack the required operation or parameters. Uses your existing permissions. Prefer dedicated tools; never bypass a denial or runner lifecycle tool. For large text responses, read the returned artifact with GET /api/assets/{assetId}/content and responseText; follow nextOffsetBytes until null.","inputSchema":{"additionalProperties":false,"properties":{"body":{"anyOf":[{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"},{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}],"description":"Request value matching the discovered schema. For JSON object or array requests, pass the object or array directly, never a JSON-encoded string. Strings are for text bodies or endpoints whose schema explicitly accepts a string."},"contentType":{"maxLength":120,"type":"string"},"files":{"items":{"additionalProperties":false,"oneOf":[{"properties":{"artifactId":{}},"required":["artifactId"]},{"properties":{"path":{}},"required":["path"]}],"properties":{"artifactId":{"type":"string"},"field":{"type":"string"},"path":{"description":"File relative to the active issue workspace. Remote files must first be uploaded as an artifact.","type":"string"}},"type":"object"},"maxItems":10,"type":"array"},"operationId":{"description":"Exact operationId returned by search_api, for example GET /api/projects/{id}. Do not guess identifiers.","maxLength":500,"minLength":1,"type":"string"},"pathParams":{"additionalProperties":{"type":"string"},"type":"object"},"query":{"additionalProperties":true,"type":"object"},"responseText":{"additionalProperties":false,"description":"GET only: return a bounded UTF-8 text window inline, including JSON as text, without saving another artifact. Offsets and limits are bytes. Use the returned nextOffsetBytes to continue; null means complete. Prefer reading a saved artifact for a stable snapshot. New live responses have a 1 GiB capture limit and a 4 GiB per-run capture budget. Existing larger assets remain readable in bounded pages. If a live response returns an artifact, continue on its content operation for a stable snapshot.","properties":{"limitBytes":{"default":24576,"maximum":24576,"minimum":4,"type":"integer"},"offsetBytes":{"default":0,"maximum":9007199254740991,"minimum":0,"type":"integer"}},"type":"object"}},"required":["operationId"],"type":"object"},"name":"call_api","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"get_agent_instruction_history","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List bounded canonical instruction revision metadata, including actor, source run, and restore origin. Use read_agent_instructions with entryFile and revisionId to inspect exact historical content.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"maxLength":2048,"minLength":1,"type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile"],"type":"object"},"name":"get_agent_instruction_history","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"always","operationId":"restore_agent_instructions","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Append a historical instruction revision as the current content using the current baseRevisionId. Requires the responsible user’s current target edit permission. History remains intact; conflicts require an explicit resolution.","inputSchema":{"additionalProperties":false,"properties":{"baseRevisionId":{"description":"Current revision read before restoring. Never replace a stale base silently.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"entryFile":{"description":"Configured relative entry filename returned by read_agent_instructions; retain it with the revision.","maxLength":4096,"minLength":1,"type":"string"},"revisionId":{"description":"Historical revision whose exact content should be restored.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"targetAgentId":{"description":"Same-company target agent. Omit to use the calling agent.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"}},"required":["entryFile","revisionId","baseRevisionId"],"type":"object"},"name":"restore_agent_instructions","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_project","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a project after considering existing projects and available repositories. repositoryIds and repositoryUrls accept multiple existing repositories. Use HTTPS GitHub repositoryUrls when an accessible repo is not in the catalog; this registers project repositories, not remote GitHub repositories. Non-code projects may omit repositories. Cannot combine repositoryIds/repositoryUrls with workspace. Reuse the idempotency key on retries.","inputSchema":{"additionalProperties":false,"properties":{"archivedAt":{"description":"Archive timestamp.","maxLength":20000,"type":["string","null"]},"color":{"description":"Project color.","maxLength":20000,"type":["string","null"]},"description":{"description":"Project outcome and context.","maxLength":20000,"type":["string","null"]},"env":{"additionalProperties":true,"type":"object"},"executionWorkspacePolicy":{"additionalProperties":true,"type":"object"},"goalId":{"description":"Goal ID.","maxLength":20000,"type":["string","null"]},"goalIds":{"description":"Goal IDs.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"icon":{"description":"Project icon.","enum":["folder","rocket","code","terminal","database","globe","package","boxes","box","layers","briefcase","compass","target","flame","zap","star","bug","wrench","hammer","lightbulb","sparkles","shield","lock","search","cog","brain","cpu","git-branch","file-code","puzzle","gem","atom","heart","mail","message-square","crown","radar","telescope","hexagon",null],"type":["string","null"]},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"leadAgentId":{"description":"Lead agent ID.","maxLength":20000,"type":["string","null"]},"name":{"description":"Project name.","maxLength":500,"minLength":1,"type":"string"},"repositoryIds":{"description":"Authorized repository IDs from list_project_repositories; may contain multiple repositories.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"repositoryUrls":{"description":"Existing HTTPS GitHub repository URLs, including repos absent from the catalog.","items":{"maxLength":2000,"pattern":"^https://github\\.com/(?!\\.{1,2}/)[A-Za-z0-9_.-]+/(?!\\.{1,2}/?$)[A-Za-z0-9_.-]+/?$","type":"string"},"maxItems":100,"type":"array"},"status":{"enum":["backlog","planned","in_progress","completed","cancelled"]},"targetDate":{"description":"Target date.","maxLength":20000,"type":["string","null"]},"workspace":{"additionalProperties":true,"type":"object"}},"required":["idempotencyKey","name"],"type":"object"},"name":"create_project","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_project_repositories","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List authorized repositories with stable IDs and names. Consider appropriate repositories before creating a project; never invent IDs.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_project_repositories","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_projects","requiredClaims":["discovery:projects:read"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List company project summaries (IDs, names, status, and up to 1,000 characters of description). Returns up to 50 projects and nextCursor; continue with cursor until it is null. Read a project's API resource for full details.","inputSchema":{"additionalProperties":false,"properties":{"cursor":{"description":"The nextCursor returned by the previous page.","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$","type":"string"},"limit":{"description":"Page size (default 50).","maximum":50,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"name":"list_projects","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"create_skill","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Create a reusable single-file skill in the company library. Supply a complete SKILL.md whose name and description match the inputs. This saves the skill and shows a card; it does not assign the skill to any agent. Reuse idempotencyKey on retries.","inputSchema":{"additionalProperties":false,"properties":{"description":{"maxLength":2000,"minLength":1,"type":"string"},"idempotencyKey":{"maxLength":240,"minLength":1,"type":"string"},"markdown":{"description":"Complete SKILL.md including name and description frontmatter and substantive instructions.","maxLength":200000,"minLength":1,"type":"string"},"name":{"description":"Lowercase skill name, matching SKILL.md frontmatter.","maxLength":120,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","type":"string"},"slug":{"description":"Optional; must equal name.","maxLength":120,"minLength":1,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","type":"string"}},"required":["idempotencyKey","name","description","markdown"],"type":"object"},"name":"create_skill","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"reassign_task","requiredClaims":["delegation:tasks:assign"],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Reassign an existing task to another company agent. Read search_tasks first and supply its current assignee and statusVersion to prevent overwriting a concurrent change. Preserve the task, documents, dependencies, and blocked/backlog status. Active work is stopped before handoff. Use a stable idempotency key for retries. Cannot reassign this run’s own task, conversations, completed tasks, or pending reviews; use create_task for delegation from the current task.","inputSchema":{"additionalProperties":false,"properties":{"assigneeActorId":{"description":"New company agent ID from list_agents; null when assigning a person.","type":["string","null"]},"assigneeUserId":{"description":"Company person ID from list_people. Mutually exclusive with assigneeActorId.","type":["string","null"]},"expectedAssigneeActorId":{"description":"Current assigneeAgentId from search_tasks; null means unassigned.","type":["string","null"]},"expectedAssigneeUserId":{"description":"Current assigneeUserId from search_tasks; omit only when there is no person assigned.","type":["string","null"]},"expectedStatusVersion":{"description":"Current statusVersion from search_tasks.","minimum":0,"type":"integer"},"idempotencyKey":{"description":"Caller-stable retry key.","maxLength":240,"minLength":1,"type":"string"},"reason":{"description":"Why the task should move and context for the new owner.","maxLength":20000,"minLength":1,"type":"string"},"taskId":{"description":"Existing task ID from search_tasks.","minLength":1,"type":"string"}},"required":["idempotencyKey","taskId","assigneeActorId","expectedAssigneeActorId","expectedStatusVersion","reason"],"type":"object"},"name":"reassign_task","outputSchema":{"additionalProperties":false,"properties":{"commandId":{"description":"Stable command identifier.","maxLength":200,"minLength":1,"type":"string"},"disposition":{"enum":["applied","duplicate"]},"entityRefs":{"description":"Entities affected by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"scheduledWakeIds":{"description":"Wake identifiers scheduled by the operation.","items":{"minLength":1,"type":"string"},"maxItems":200,"type":"array","uniqueItems":true},"stateRevision":{"minimum":0,"type":"integer"}},"required":["commandId","disposition","stateRevision","entityRefs","scheduledWakeIds"],"type":"object"}},{"annotations":{"exposure":"optional","operationId":"update_skill","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Replace an existing company skill's complete SKILL.md using the current version as a guard. Reuse idempotencyKey on lost-response retries; read again after a version conflict.","inputSchema":{"additionalProperties":false,"properties":{"expectedVersionId":{"minLength":1,"type":"string"},"idempotencyKey":{"maxLength":240,"minLength":1,"type":"string"},"markdown":{"maxLength":200000,"minLength":1,"type":"string"},"skillId":{"minLength":1,"type":"string"}},"required":["skillId","markdown","expectedVersionId","idempotencyKey"],"type":"object"},"name":"update_skill","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_task_title","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Set a concise, descriptive title for the active task. When its titleNeedsGeneration is true, call this early with onlyIfProvisional: true to replace the initial prompt slice without overwriting a user's title. Use false only for an intentional rename. This changes no task status, ownership, or description.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Reuse this key on retries.","maxLength":240,"minLength":1,"type":"string"},"onlyIfProvisional":{"description":"True for automatic initial naming; preserves any title already chosen by a user or agent.","type":"boolean"},"title":{"description":"Short title describing the requested outcome.","maxLength":240,"minLength":1,"type":"string"}},"required":["idempotencyKey","title","onlyIfProvisional"],"type":"object"},"name":"set_task_title","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"set_task_monitor","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Schedule, replace, or clear a one-shot task monitor. Omit taskId for your current task; other tasks must also be assigned to you and in progress or review. Supply a future nextCheckAt and notes describing the next check, or monitor: null to clear. A successful receipt reports the persisted monitor; a replay reports its current state without re-arming it. After scheduling your current task, end the turn with paperclip_finish: yielded and continuation.kind monitor. Paperclip will wake you with issue_monitor_due; do not sleep, poll, mark done, or use call_api for this wait.","inputSchema":{"additionalProperties":false,"properties":{"idempotencyKey":{"description":"Reuse with identical arguments on retries.","maxLength":240,"minLength":1,"type":"string"},"monitor":{"additionalProperties":false,"properties":{"externalRef":{"maxLength":500,"minLength":1,"type":["string","null"]},"kind":{"enum":["external_service",null],"type":["string","null"]},"maxAttempts":{"description":"Optional cumulative attempt limit for this task's monitor.","maximum":100,"minimum":1,"type":["integer","null"]},"nextCheckAt":{"description":"Future UTC timestamp for the next check. This is one shot, not a recurring interval.","type":"string"},"notes":{"description":"What to check on the next run; do not include secrets.","maxLength":500,"minLength":1,"type":"string"},"recoveryPolicy":{"enum":["wake_owner","create_recovery_issue","escalate_to_board",null],"type":["string","null"]},"serviceName":{"maxLength":120,"minLength":1,"type":["string","null"]},"timeoutAt":{"description":"Optional deadline later than nextCheckAt.","type":["string","null"]}},"required":["nextCheckAt","notes"],"type":["object","null"]},"taskId":{"description":"Owned task UUID. Omit for the active task.","type":"string"}},"required":["idempotencyKey","monitor"],"type":"object"},"name":"set_task_monitor","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"submit_complaint","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Yeet a brief, completely unstructured complaint about agent tooling or the experience of doing agent work. Preserve your raw voice: fragments, profanity, all caps, and exasperation are fair game. No diagnosis, solution, diplomacy, category, or template required. Use submit_suggestion when you want to express a useful improvement. Submit proactively without routine permission requests, previews, or announcements, then immediately continue the primary task. Answer truthfully if the user asks about feedback or what you submitted. Stored in this instance with company, agent, run, and task attribution; not anonymous. Remove secrets, private prompts, customer data, and personal blame; do not forward untrusted text or logs indiscriminately. On failure, continue without retrying or complaining about submission. Never submit the same incident through both tools.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Free-form feedback. Keep it brief; no required fields, labels, or template.","maxLength":524288,"minLength":1,"type":"string"},"idempotencyKey":{"description":"A stable key for this submission. Transport replay reuses it; do not retry a failed or uncertain submission yourself.","maxLength":240,"minLength":1,"type":"string"}},"required":["body","idempotencyKey"],"type":"object"},"name":"submit_complaint","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"submit_suggestion","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Quietly report a concrete improvement that would make agents more effective. Use for material, generalizable friction directly observed during this run. Explain what happened, its impact, and a plausible improvement in your own words, with minimal useful sanitized context. No required format. Avoid duplicate root causes and aim for at most three suggestions per run. Use submit_complaint for the raw reaction. Submit proactively without routine permission requests, previews, or announcements, then immediately continue the primary task. Answer truthfully if the user asks about feedback or what you submitted. Stored in this instance with company, agent, run, and task attribution; not anonymous. Remove secrets, private prompts, customer data, and personal blame; do not forward untrusted text or logs indiscriminately. On failure, continue without retrying or complaining about submission. Never submit the same incident through both tools.","inputSchema":{"additionalProperties":false,"properties":{"body":{"description":"Free-form feedback. Keep it brief; no required fields, labels, or template.","maxLength":524288,"minLength":1,"type":"string"},"idempotencyKey":{"description":"A stable key for this submission. Transport replay reuses it; do not retry a failed or uncertain submission yourself.","maxLength":240,"minLength":1,"type":"string"}},"required":["body","idempotencyKey"],"type":"object"},"name":"submit_suggestion","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"get_workspace","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Inspect current task files, repositories, pending selection, binding revision, and preparation capabilities. AGENT_HOME is personal instructions and memory; task outputs belong in the workspace.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"get_workspace","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"list_workspaces","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"List authorized reusable workspaces. A project is optional; choosing a workspace does not assign a project.","inputSchema":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"name":"list_workspaces","outputSchema":{"items":{"additionalProperties":true,"type":"object"},"type":"array"}},{"annotations":{"exposure":"optional","operationId":"select_workspace","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Request a different task workspace at the next normal admission. Does not interrupt or wake the current run. Inspect the binding revision first and reuse requestKey on retries; existing files stay in their current folder.","inputSchema":{"additionalProperties":false,"properties":{"expectedBindingRevision":{"minimum":0,"type":"integer"},"requestKey":{"description":"Stable retry key.","minLength":1,"type":"string"},"selection":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"task_directory"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"existing"},"workspaceId":{"description":"Existing authorized workspace ID.","minLength":1,"type":"string"}},"required":["kind","workspaceId"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"configured_source"},"mode":{"enum":["shared","managed_isolated"]},"projectWorkspaceId":{"description":"Configured source workspace ID.","minLength":1,"type":"string"}},"required":["kind","projectWorkspaceId","mode"],"type":"object"}]}},"required":["selection","expectedBindingRevision","requestKey"],"type":"object"},"name":"select_workspace","outputSchema":{"additionalProperties":true,"type":"object"}},{"annotations":{"exposure":"optional","operationId":"prepare_repository","requiredClaims":[],"semanticContract":"paperclip.semantic-tool.v1","version":1},"description":"Request an authorized repository inside task files at the next normal admission, without creating a project. Discover repository IDs first. Does not interrupt or wake the current run. Reuse requestKey; a conflicting ref never resets existing work.","inputSchema":{"additionalProperties":false,"properties":{"ref":{"description":"Optional Git branch, tag, or commit. Default HEAD is pinned once.","minLength":1,"type":"string"},"repository":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"description":"Authorized ID from list_project_repositories.","minLength":1,"type":"string"},"kind":{"const":"catalog"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"url"},"url":{"description":"Existing HTTPS GitHub repository URL; uncataloged URLs use public anonymous access only.","minLength":1,"type":"string"}},"required":["kind","url"],"type":"object"}]},"requestKey":{"description":"Stable retry key.","minLength":1,"type":"string"}},"required":["repository","requestKey"],"type":"object"},"name":"prepare_repository","outputSchema":{"additionalProperties":true,"type":"object"}}] diff --git a/packages/paperclip-runner/generated/semantic-action-catalog.json b/packages/paperclip-runner/generated/semantic-action-catalog.json index 822984b3d77..e45555acc0e 100644 --- a/packages/paperclip-runner/generated/semantic-action-catalog.json +++ b/packages/paperclip-runner/generated/semantic-action-catalog.json @@ -1883,6 +1883,236 @@ "title": "Set task dependencies", "version": 1 }, + { + "allowedModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "description": "Inspect current task files, repositories, pending selection, binding revision, and preparation capabilities. AGENT_HOME is personal instructions and memory; task outputs belong in the workspace.", + "effect": "read", + "inputSchema": { + "additionalProperties": false, + "properties": {}, + "required": [], + "type": "object" + }, + "operationId": "get_workspace", + "outputSchema": { + "additionalProperties": true, + "type": "object" + }, + "placement": "optional", + "requiredClaims": [], + "schema": "paperclip.semantic-action.v1", + "title": "Inspect task workspace", + "version": 1 + }, + { + "allowedModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "description": "List authorized reusable workspaces. A project is optional; choosing a workspace does not assign a project.", + "effect": "read", + "inputSchema": { + "additionalProperties": false, + "properties": {}, + "required": [], + "type": "object" + }, + "operationId": "list_workspaces", + "outputSchema": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array" + }, + "placement": "optional", + "requiredClaims": [], + "schema": "paperclip.semantic-action.v1", + "title": "List available workspaces", + "version": 1 + }, + { + "allowedModes": [ + "standard", + "skill_test" + ], + "description": "Request a different task workspace at the next normal admission. Does not interrupt or wake the current run. Inspect the binding revision first and reuse requestKey on retries; existing files stay in their current folder.", + "effect": "write", + "inputSchema": { + "additionalProperties": false, + "properties": { + "expectedBindingRevision": { + "minimum": 0, + "type": "integer" + }, + "requestKey": { + "description": "Stable retry key.", + "minLength": 1, + "type": "string" + }, + "selection": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "task_directory" + } + }, + "required": [ + "kind" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "existing" + }, + "workspaceId": { + "description": "Existing authorized workspace ID.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "workspaceId" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "configured_source" + }, + "mode": { + "enum": [ + "shared", + "managed_isolated" + ] + }, + "projectWorkspaceId": { + "description": "Configured source workspace ID.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "projectWorkspaceId", + "mode" + ], + "type": "object" + } + ] + } + }, + "required": [ + "selection", + "expectedBindingRevision", + "requestKey" + ], + "type": "object" + }, + "operationId": "select_workspace", + "outputSchema": { + "additionalProperties": true, + "type": "object" + }, + "placement": "optional", + "requiredClaims": [], + "schema": "paperclip.semantic-action.v1", + "title": "Select task workspace", + "version": 1 + }, + { + "allowedModes": [ + "standard", + "skill_test" + ], + "description": "Request an authorized repository inside task files at the next normal admission, without creating a project. Discover repository IDs first. Does not interrupt or wake the current run. Reuse requestKey; a conflicting ref never resets existing work.", + "effect": "write", + "inputSchema": { + "additionalProperties": false, + "properties": { + "ref": { + "description": "Optional Git branch, tag, or commit. Default HEAD is pinned once.", + "minLength": 1, + "type": "string" + }, + "repository": { + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "id": { + "description": "Authorized ID from list_project_repositories.", + "minLength": 1, + "type": "string" + }, + "kind": { + "const": "catalog" + } + }, + "required": [ + "kind", + "id" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "kind": { + "const": "url" + }, + "url": { + "description": "Existing HTTPS GitHub repository URL; uncataloged URLs use public anonymous access only.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "url" + ], + "type": "object" + } + ] + }, + "requestKey": { + "description": "Stable retry key.", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "repository", + "requestKey" + ], + "type": "object" + }, + "operationId": "prepare_repository", + "outputSchema": { + "additionalProperties": true, + "type": "object" + }, + "placement": "optional", + "requiredClaims": [], + "schema": "paperclip.semantic-action.v1", + "title": "Prepare task repository", + "version": 1 + }, { "allowedModes": [ "standard", diff --git a/packages/paperclip-runner/protocol/fixtures/evals/native-execution-seeded.json b/packages/paperclip-runner/protocol/fixtures/evals/native-execution-seeded.json index bbfdfd46930..7a12f8c3b22 100644 --- a/packages/paperclip-runner/protocol/fixtures/evals/native-execution-seeded.json +++ b/packages/paperclip-runner/protocol/fixtures/evals/native-execution-seeded.json @@ -24,7 +24,7 @@ "prpVersion": 1, "nativeExecutionVersion": 1, "catalogVersion": 1, - "catalogSha256": "sha256:ff3e2bdc66e564cef9500f0940b9afe9d1878e7c8ca3d5a5a0c986b23ffe79de", + "catalogSha256": "sha256:73f19996d9e1b46e957ba732354eb24daf4e96776ae121ea059acfaf01f56ef6", "driverContractVersion": 1, "driverKind": "paperclip-deterministic", "driverVersion": "1.0.0" diff --git a/packages/paperclip-runner/protocol/manifest.json b/packages/paperclip-runner/protocol/manifest.json index 2eefd4f3069..234c6e49139 100644 --- a/packages/paperclip-runner/protocol/manifest.json +++ b/packages/paperclip-runner/protocol/manifest.json @@ -175,7 +175,7 @@ }, { "path": "fixtures/evals/native-execution-seeded.json", - "sha256": "248c366cd272e1cc13506886bee4ee15fd63813280583b5859011446035ebbbb", + "sha256": "872f556fd39a4b7e50a902d09c8b42f8fc729030d03642983fa16872f623e1ef", "expectation": "accept", "compatibilityCase": "canonical" }, diff --git a/packages/paperclip-runner/scripts/check-capability-inventory.test.mjs b/packages/paperclip-runner/scripts/check-capability-inventory.test.mjs index 9da72f5f233..66ff6d15ec4 100644 --- a/packages/paperclip-runner/scripts/check-capability-inventory.test.mjs +++ b/packages/paperclip-runner/scripts/check-capability-inventory.test.mjs @@ -42,7 +42,7 @@ function validInventories() { schemaVersion: 2, inventoryRole: "normative", generatedFrom: ["skills/paperclip/SKILL.md"], - rows: Array.from({ length: 162 }, (_, index) => row(`capability-${index}`)), + rows: Array.from({ length: 164 }, (_, index) => row(`capability-${index}`)), }, evaluations: { schemaVersion: 2, diff --git a/packages/paperclip-runner/scripts/lib/capability-inventory.mjs b/packages/paperclip-runner/scripts/lib/capability-inventory.mjs index c56a53d2081..940961deb20 100644 --- a/packages/paperclip-runner/scripts/lib/capability-inventory.mjs +++ b/packages/paperclip-runner/scripts/lib/capability-inventory.mjs @@ -247,7 +247,7 @@ export async function buildMcpInventory(repoRoot) { export function validateInventories(inventories) { const errors = []; - const expectedCounts = { capabilities: 162, evaluations: 106, legacyMcpAliases: 42 }; + const expectedCounts = { capabilities: 164, evaluations: 106, legacyMcpAliases: 42 }; const normativeNames = ["capabilities", "evaluations"]; const normativeRows = new Map(); const globalNormativeIds = new Set(); diff --git a/packages/paperclip-runner/spec/capability/capabilities.yaml b/packages/paperclip-runner/spec/capability/capabilities.yaml index 33a91219114..7899ff444d0 100644 --- a/packages/paperclip-runner/spec/capability/capabilities.yaml +++ b/packages/paperclip-runner/spec/capability/capabilities.yaml @@ -315,11 +315,11 @@ ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:547", + "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/SKILL.md:547", - "title": "Issue Workspace Runtime Controls", - "expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.", + "title": "Task Files and Workspace Choices", + "expectedSemantics": "Skill guidance headed “Task Files and Workspace Choices”.", "primaryDisposition": "optional_agent_tool", "requiredGrants": [], "assertionClasses": [ @@ -330,9 +330,24 @@ ] }, { - "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:554", + "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:554", + "sourceAnchor": "skills/paperclip/SKILL.md:553", + "title": "Issue Workspace Runtime Controls", + "expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.", + "primaryDisposition": "optional_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/SKILL.md:553" + ] + }, + { + "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/SKILL.md:560", "title": "Proposing Credentials Safely", "expectedSemantics": "Skill guidance headed “Proposing Credentials Safely”.", "primaryDisposition": "optional_agent_tool", @@ -341,13 +356,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:554" + "skill:skills/paperclip/SKILL.md:560" ] }, { - "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:561", + "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:567", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:561", + "sourceAnchor": "skills/paperclip/SKILL.md:567", "title": "Reading Granted Secrets", "expectedSemantics": "Skill guidance headed “Reading Granted Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -356,13 +371,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:561" + "skill:skills/paperclip/SKILL.md:567" ] }, { - "id": "skill:skills/paperclip/SKILL.md:critical-rules:587", + "id": "skill:skills/paperclip/SKILL.md:critical-rules:593", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:587", + "sourceAnchor": "skills/paperclip/SKILL.md:593", "title": "Critical Rules", "expectedSemantics": "Skill guidance headed “Critical Rules”.", "primaryDisposition": "optional_agent_tool", @@ -371,13 +386,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:587" + "skill:skills/paperclip/SKILL.md:593" ] }, { - "id": "skill:skills/paperclip/SKILL.md:comment-style-required:608", + "id": "skill:skills/paperclip/SKILL.md:comment-style-required:614", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:608", + "sourceAnchor": "skills/paperclip/SKILL.md:614", "title": "Comment Style (Required)", "expectedSemantics": "Skill guidance headed “Comment Style (Required)”.", "primaryDisposition": "always_agent_tool", @@ -386,13 +401,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:608" + "skill:skills/paperclip/SKILL.md:614" ] }, { - "id": "skill:skills/paperclip/SKILL.md:update:640", + "id": "skill:skills/paperclip/SKILL.md:update:646", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:640", + "sourceAnchor": "skills/paperclip/SKILL.md:646", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -401,13 +416,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:640" + "skill:skills/paperclip/SKILL.md:646" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:650", + "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:650", + "sourceAnchor": "skills/paperclip/SKILL.md:656", "title": "Task Documents and Deliverables", "expectedSemantics": "Skill guidance headed “Task Documents and Deliverables”.", "primaryDisposition": "always_agent_tool", @@ -416,13 +431,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:650" + "skill:skills/paperclip/SKILL.md:656" ] }, { - "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:664", + "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:664", + "sourceAnchor": "skills/paperclip/SKILL.md:670", "title": "Planning (Required when planning requested)", "expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.", "primaryDisposition": "optional_agent_tool", @@ -431,13 +446,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:664" + "skill:skills/paperclip/SKILL.md:670" ] }, { - "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:695", + "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:695", + "sourceAnchor": "skills/paperclip/SKILL.md:701", "title": "Key Endpoints (Hot Routes)", "expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.", "primaryDisposition": "optional_agent_tool", @@ -446,13 +461,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:695" + "skill:skills/paperclip/SKILL.md:701" ] }, { - "id": "skill:skills/paperclip/SKILL.md:searching-issues:724", + "id": "skill:skills/paperclip/SKILL.md:searching-issues:730", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:724", + "sourceAnchor": "skills/paperclip/SKILL.md:730", "title": "Searching Issues", "expectedSemantics": "Skill guidance headed “Searching Issues”.", "primaryDisposition": "optional_agent_tool", @@ -461,13 +476,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:724" + "skill:skills/paperclip/SKILL.md:730" ] }, { - "id": "skill:skills/paperclip/SKILL.md:full-reference:734", + "id": "skill:skills/paperclip/SKILL.md:full-reference:740", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:734", + "sourceAnchor": "skills/paperclip/SKILL.md:740", "title": "Full Reference", "expectedSemantics": "Skill guidance headed “Full Reference”.", "primaryDisposition": "optional_agent_tool", @@ -476,13 +491,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:734" + "skill:skills/paperclip/SKILL.md:740" ] }, { - "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:738", + "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:738", + "sourceAnchor": "skills/paperclip/SKILL.md:744", "title": "Conversational confirmation answers", "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", @@ -491,13 +506,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:738" + "skill:skills/paperclip/SKILL.md:744" ] }, { - "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:755", + "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:761", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:755", + "sourceAnchor": "skills/paperclip/SKILL.md:761", "title": "Incidental feedback", "expectedSemantics": "Skill guidance headed “Incidental feedback”.", "primaryDisposition": "optional_agent_tool", @@ -506,7 +521,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:755" + "skill:skills/paperclip/SKILL.md:761" ] }, { @@ -2040,11 +2055,11 @@ ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:control-services:23", + "id": "skill:skills/paperclip/references/issue-workspaces.md:choose-files-and-repositories:23", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:23", - "title": "Control Services", - "expectedSemantics": "Skill guidance headed “Control Services”.", + "title": "Choose Files and Repositories", + "expectedSemantics": "Skill guidance headed “Choose Files and Repositories”.", "primaryDisposition": "optional_agent_tool", "requiredGrants": [], "assertionClasses": [ @@ -2055,9 +2070,24 @@ ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:28", + "id": "skill:skills/paperclip/references/issue-workspaces.md:control-services:37", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:28", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:37", + "title": "Control Services", + "expectedSemantics": "Skill guidance headed “Control Services”.", + "primaryDisposition": "optional_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/issue-workspaces.md:37" + ] + }, + { + "id": "skill:skills/paperclip/references/issue-workspaces.md:start-all-configured-services-waits-for-configured-readiness-checks:42", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:42", "title": "Start all configured services; waits for configured readiness checks.", "expectedSemantics": "Skill guidance headed “Start all configured services; waits for configured readiness checks.”.", "primaryDisposition": "optional_agent_tool", @@ -2066,13 +2096,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/issue-workspaces.md:28" + "skill:skills/paperclip/references/issue-workspaces.md:42" ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:36", + "id": "skill:skills/paperclip/references/issue-workspaces.md:restart-all-configured-services:50", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:36", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:50", "title": "Restart all configured services.", "expectedSemantics": "Skill guidance headed “Restart all configured services.”.", "primaryDisposition": "optional_agent_tool", @@ -2081,13 +2111,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/issue-workspaces.md:36" + "skill:skills/paperclip/references/issue-workspaces.md:50" ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:44", + "id": "skill:skills/paperclip/references/issue-workspaces.md:stop-all-running-services:58", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:44", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:58", "title": "Stop all running services.", "expectedSemantics": "Skill guidance headed “Stop all running services.”.", "primaryDisposition": "optional_agent_tool", @@ -2096,13 +2126,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/issue-workspaces.md:44" + "skill:skills/paperclip/references/issue-workspaces.md:58" ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:read-the-url:63", + "id": "skill:skills/paperclip/references/issue-workspaces.md:read-the-url:77", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:63", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:77", "title": "Read the URL", "expectedSemantics": "Skill guidance headed “Read the URL”.", "primaryDisposition": "optional_agent_tool", @@ -2111,13 +2141,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/issue-workspaces.md:63" + "skill:skills/paperclip/references/issue-workspaces.md:77" ] }, { - "id": "skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:72", + "id": "skill:skills/paperclip/references/issue-workspaces.md:mcp-tools:86", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:72", + "sourceAnchor": "skills/paperclip/references/issue-workspaces.md:86", "title": "MCP Tools", "expectedSemantics": "Skill guidance headed “MCP Tools”.", "primaryDisposition": "optional_agent_tool", @@ -2126,7 +2156,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/issue-workspaces.md:72" + "skill:skills/paperclip/references/issue-workspaces.md:86" ] }, { diff --git a/packages/paperclip-runner/spec/capability/protocol-coverage.json b/packages/paperclip-runner/spec/capability/protocol-coverage.json index e61e64a4b37..2a23747b2e1 100644 --- a/packages/paperclip-runner/spec/capability/protocol-coverage.json +++ b/packages/paperclip-runner/spec/capability/protocol-coverage.json @@ -7,10 +7,86 @@ "src/scenarios/scenario-plan.ts" ], "counts": { - "actions": 57, + "actions": 61, "legacyRequirements": 106 }, "actions": [ + { + "id": "get_workspace", + "ownership": "optional_agent_tool", + "surfaces": [ + "live" + ], + "legacyAliases": [], + "contractCase": "protocol-action:get_workspace", + "contractOwner": "src/catalog/protocol-action-contracts.test.ts::get_workspace has a schema-valid canonical example and every declared projection", + "legacyBehavioralCases": [], + "deterministicCases": [ + "protocol-action:get_workspace" + ], + "legacyRequirementCases": [], + "deterministicOwners": [ + "src/catalog/protocol-action-contracts.test.ts::get_workspace has a schema-valid canonical example and every declared projection", + "src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity" + ] + }, + { + "id": "list_workspaces", + "ownership": "optional_agent_tool", + "surfaces": [ + "live" + ], + "legacyAliases": [], + "contractCase": "protocol-action:list_workspaces", + "contractOwner": "src/catalog/protocol-action-contracts.test.ts::list_workspaces has a schema-valid canonical example and every declared projection", + "legacyBehavioralCases": [], + "deterministicCases": [ + "protocol-action:list_workspaces" + ], + "legacyRequirementCases": [], + "deterministicOwners": [ + "src/catalog/protocol-action-contracts.test.ts::list_workspaces has a schema-valid canonical example and every declared projection", + "src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity" + ] + }, + { + "id": "select_workspace", + "ownership": "optional_agent_tool", + "surfaces": [ + "live" + ], + "legacyAliases": [], + "contractCase": "protocol-action:select_workspace", + "contractOwner": "src/catalog/protocol-action-contracts.test.ts::select_workspace has a schema-valid canonical example and every declared projection", + "legacyBehavioralCases": [], + "deterministicCases": [ + "protocol-action:select_workspace" + ], + "legacyRequirementCases": [], + "deterministicOwners": [ + "src/catalog/protocol-action-contracts.test.ts::select_workspace has a schema-valid canonical example and every declared projection", + "src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity" + ] + }, + { + "id": "prepare_repository", + "ownership": "optional_agent_tool", + "surfaces": [ + "live" + ], + "legacyAliases": [], + "contractCase": "protocol-action:prepare_repository", + "contractOwner": "src/catalog/protocol-action-contracts.test.ts::prepare_repository has a schema-valid canonical example and every declared projection", + "legacyBehavioralCases": [], + "deterministicCases": [ + "protocol-action:prepare_repository" + ], + "legacyRequirementCases": [], + "deterministicOwners": [ + "src/catalog/protocol-action-contracts.test.ts::prepare_repository has a schema-valid canonical example and every declared projection", + "src/scenarios/scenario-explorer.test.ts::renders every scenario with exposure, control plane, authorization, diff, and parity" + ] + }, { "id": "set_task_title", "ownership": "optional_agent_tool", diff --git a/packages/paperclip-runner/spec/operation-groups/source.json b/packages/paperclip-runner/spec/operation-groups/source.json index 24cadcb043a..9f2f0a69646 100644 --- a/packages/paperclip-runner/spec/operation-groups/source.json +++ b/packages/paperclip-runner/spec/operation-groups/source.json @@ -77,8 +77,8 @@ }, { "id": "workspace_runtime", - "description": "Inspect and control the active issue workspace runtime.", - "operationIds": ["get_workspace_runtime", "control_workspace_service"] + "description": "Inspect and select task workspaces, prepare repositories, and control workspace runtime services.", + "operationIds": ["get_workspace_runtime", "control_workspace_service", "get_workspace", "list_workspaces", "select_workspace", "prepare_repository"] }, { "id": "wake_scheduling", @@ -257,12 +257,12 @@ "legacyGroup": 12, "name": "Errors and critical rules", "owner": "runner/control plane + optional workspace/wake tools", - "operationIds": ["get_workspace_runtime", "control_workspace_service", "schedule_wake", "inspect_operation_result"], + "operationIds": ["get_workspace_runtime", "control_workspace_service", "get_workspace", "list_workspaces", "select_workspace", "prepare_repository", "schedule_wake", "inspect_operation_result"], "controlPlaneOperationIds": ["release_task", "enforce_budget", "persist_run", "replay_run", "reconcile_run"], "realSurface": "workspace runtime, monitor/recovery, budget, run persistence/replay, release, and terminal services", "mockStateDomains": ["workspace", "budget", "run", "wake", "audit", "idempotency", "fault"], "prpEvidence": "runtime/workspace/attention/run lifecycle, typed denials, replay facts, and terminal causality", - "gap": "Budget stop reasons and semantic denial/conflict receipts require additive v1 envelopes; inspect_operation_result remains scenario-only." + "gap": "Budget stop reasons and semantic denial/conflict receipts require additive v1 envelopes; inspect_operation_result remains scenario-only. Task workspace inspection, discovery, selection and repository preparation use live authenticated service contracts; the legacy scenarios do not claim coverage for these operations." }, { "id": "rf", diff --git a/packages/paperclip-runner/spec/paperclip-agent-operation-groups.md b/packages/paperclip-runner/spec/paperclip-agent-operation-groups.md index e9096b92314..bbe9ad44921 100644 --- a/packages/paperclip-runner/spec/paperclip-agent-operation-groups.md +++ b/packages/paperclip-runner/spec/paperclip-agent-operation-groups.md @@ -6,7 +6,7 @@ Status: canonical explanatory contract for the Paperclip runner V1 surface. This document keeps three independent meanings of **group** separate. PRP families describe wire evidence and controller commands; capability placement decides who owns an operation; behavioral eval groups organize the 106 scenario corpus. None of the three axes can be used as a substitute for another. -The generated totals are **105 PRP events in 31 event families**, **18 controller commands in 7 command families**, **10 control-plane operations**, **57 reconciled semantic operations** (18 always, 39 optional), and **106 scenarios in 16 behavior groups**. +The generated totals are **105 PRP events in 31 event families**, **18 controller commands in 7 command families**, **10 control-plane operations**, **61 reconciled semantic operations** (18 always, 43 optional), and **106 scenarios in 16 behavior groups**. ## Axis 1: PRP v1 event and command families @@ -87,7 +87,7 @@ Placement has exactly three outcomes: `answer_status_question`, `block_task`, `finish_task`, `get_agent_instruction_history`, `get_task_context`, `get_task_history`, `inspect_operation_result`, `list_document_revisions`, `list_documents`, `read_agent_instructions`, `read_document`, `register_deliverable`, `report_progress`, `request_human_input`, `request_review`, `restore_agent_instructions`, `update_agent_instructions`, `write_document`. -### Optional operations (39) and grant groups (14) +### Optional operations (43) and grant groups (14) Grant groups are documentation/exposure bundles, not additional authority. The operation descriptor's exact `requiredClaims` remains decisive. @@ -99,7 +99,7 @@ Grant groups are documentation/exposure bundles, not additional authority. The o | `delegation_dependencies` | `create_task`
`reassign_task`
`set_dependencies`
`hire_agent` | `delegation:agents:create`
`delegation:tasks:assign`
`delegation:tasks:create`
`dependencies:write` | Create or reassign delegated work and maintain dependency edges. | | `governance` | `list_approvals`
`get_approval`
`get_approval_context`
`request_approval`
`decide_approval`
`comment_on_approval` | `governance:approvals:comment`
`governance:approvals:decide`
`governance:approvals:read`
`governance:approvals:request` | Read, request, comment on, and decide approvals under governed-action checks. | | `cases` | `list_cases`
`upsert_case` | `cases:read`
`cases:write` | Read and update case summaries without reusing issue-document authority. | -| `workspace_runtime` | `get_workspace_runtime`
`control_workspace_service` | `workspace:control`
`workspace:read` | Inspect and control the active issue workspace runtime. | +| `workspace_runtime` | `get_workspace_runtime`
`control_workspace_service`
`get_workspace`
`list_workspaces`
`select_workspace`
`prepare_repository` | `workspace:control`
`workspace:read` | Inspect and select task workspaces, prepare repositories, and control workspace runtime services. | | `wake_scheduling` | `schedule_wake`
`set_task_monitor` | `control_plane:wakes` | Schedule a bounded continuation wake when the current task owns the future check. | | `routines` | `list_routines`
`manage_routine` | `routines:read`
`routines:write` | Inspect or manage company routines. | | `company_skills` | `create_skill`
`update_skill`
`list_company_skills`
`sync_company_skills` | `company_skills:read`
`company_skills:write` | Create, update, or inspect company skills and synchronize the current agent's skills. | @@ -133,6 +133,7 @@ Grant groups are documentation/exposure bundles, not additional authority. The o | `get_approval_context` | `optional_agent_tool` | `governance:approvals:read` | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | inline/no mapping | `live`
`live_codex` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | | `get_task_context` | `always_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `context_read:active_task` | `scenario` + `live`
`live_codex` | `PaperclipRunnerToolAuthority active issue/run + accepted plan revision`
bound company/assignment query plus exact accepted document revision projection
catalog PRP status: `bound` | | `get_task_history` | `always_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `snapshot_read:active_task_history` | `scenario` + `live`
`live_codex` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | +| `get_workspace` | `optional_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated task workspace routes and durable selection/repository receipts.
catalog PRP status: `bound` | | `get_workspace_runtime` | `optional_agent_tool` | `workspace:read` | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `snapshot_read:active_task_workspace` | `scenario` + `live`
`live_codex` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | | `hire_agent` | `optional_agent_tool` | `delegation:agents:create` | `standard`
`skill_test` | `company_write` | `required` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated PRP tool input/result and agent-hire state diff with activity record.
catalog PRP status: `bound` | | `inspect_operation_result` | `always_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `operation_result` | `scenario`
`scenario_mock` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | @@ -147,7 +148,9 @@ Grant groups are documentation/exposure bundles, not additional authority. The o | `list_projects` | `optional_agent_tool` | `discovery:projects:read` | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `mock_extension:discovery.projects` | `scenario` + `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated project tools, persisted projects and repository workspaces, and run-bound activity.
catalog PRP status: `bound` | | `list_routines` | `optional_agent_tool` | `routines:read` | `standard`
`skill_test` | `read` | `none` | no | `mock_extension:routines.list` | `scenario`
`scenario_mock` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | | `list_secret_metadata` | `optional_agent_tool` | `secrets:metadata:read` | `standard`
`skill_test` | `read` | `none` | no | `mock_extension:secrets.metadata` | `scenario`
`scenario_mock` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | +| `list_workspaces` | `optional_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated task workspace routes and durable selection/repository receipts.
catalog PRP status: `bound` | | `manage_routine` | `optional_agent_tool` | `routines:write` | `standard`
`skill_test` | `admin` | `required` | no | `mock_extension:routines.manage` | `scenario`
`scenario_mock` | `unbound`
company admin/portability item event plus audit record
catalog PRP status: `audit_pending` | +| `prepare_repository` | `optional_agent_tool` | none | `standard`
`skill_test` | `write` | `required` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated task workspace routes and durable selection/repository receipts.
catalog PRP status: `bound` | | `read_agent_instructions` | `always_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | inline/no mapping | `live`
`live_codex` | `agentInstructionRevisionService`
tool-result item event plus canonical revision receipt for writes; no scenario mock coverage
catalog PRP status: `bound` | | `read_document` | `always_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `snapshot_read:active_task_document` | `scenario` + `live`
`live_codex` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | | `read_secret_value` | `optional_agent_tool` | `secrets:values:read` | `standard`
`skill_test` | `secret_read` | `none` | yes | `mock_extension:secrets.value` | `scenario`
`scenario_mock` | `unbound`
redacted tool-result item event; secret value never reaches the wire
catalog PRP status: `audit_pending` | @@ -161,6 +164,7 @@ Grant groups are documentation/exposure bundles, not additional authority. The o | `schedule_wake` | `optional_agent_tool` | `control_plane:wakes` | `standard`
`skill_test` | `task_write` | `required` | no | inline/no mapping | `live`
`live_codex` | `unbound`
semantic-operation item event plus active-task state diff, work-assessment, and issue-status-decision events
catalog PRP status: `audit_pending` | | `search_api` | `optional_agent_tool` | `api:discover` | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated PRP tool input/result and existing HTTP route authorization/activity records.
catalog PRP status: `bound` | | `search_tasks` | `optional_agent_tool` | `discovery:tasks:read` | `standard`
`ask`
`planning`
`skill_test` | `read` | `none` | no | `snapshot_read:company_tasks` | `scenario` + `live`
`live_codex` | `unbound`
read projection surfaced via a tool-result item event; no control-plane state diff
catalog PRP status: `audit_pending` | +| `select_workspace` | `optional_agent_tool` | none | `standard`
`skill_test` | `write` | `required` | no | inline/no mapping | `live`
`live_codex` | `PaperclipRunnerToolAuthority`
Authenticated task workspace routes and durable selection/repository receipts.
catalog PRP status: `bound` | | `set_dependencies` | `optional_agent_tool` | `dependencies:write` | `standard`
`skill_test` | `company_write` | `required` | no | `semantic_command:set_dependencies` | `scenario` + `live`
`live_codex` | `issues.update.blockedByIssueIds`
semantic-operation item event plus company-entity state diff and audit record
catalog PRP status: `bound` | | `set_task_monitor` | `optional_agent_tool` | none | `standard` | `task_write` | `required` | no | inline/no mapping | `live`
`live_codex` | `prepareIssueMonitorUpdate`
Transactional persisted issue monitor, audit activity and idempotent run receipt.
catalog PRP status: `bound` | | `set_task_title` | `optional_agent_tool` | none | `standard`
`ask`
`planning`
`skill_test` | `task_write` | `required` | no | inline/no mapping | `live`
`live_codex` | `setIssueTitle`
Run-bound title update and transactional activity record.
catalog PRP status: `bound` | @@ -191,7 +195,7 @@ Behavior groups describe expected outcomes and trajectories. They do not grant t | [`ix` — Interactions](#behavior-group-ix-interactions) | always tools + addressed resolver | `request_human_input` | `route_wake` | issue-thread interaction create/respond/accept/reject/withdraw services | `task`
`documents`
`interactions`
`wake`
`actor`
`idempotency` | 9 | interaction proposal/materialization/response/delivery and attention events | Production binding and semantic resolution receipts are unbound. | | [`ap` — Approvals](#behavior-group-ap-approvals) | optional governance tools + governed approver | `list_approvals`
`get_approval`
`get_approval_context`
`request_approval`
`decide_approval`
`comment_on_approval` | `route_wake`
`append_audit_record` | company approval, decision, issue-link, comment, and governed-action services | `company`
`task`
`approvals`
`actor`
`wake`
`audit`
`idempotency` | 6 | governed semantic receipts, audit references, and attention/continuation linkage | Production binding and additive governed-action receipts are unbound; board-only authority stays outside grants. | | [`ar` — Artifacts](#behavior-group-ar-artifacts) | always tools + artifact/work-product services | `register_deliverable` | `append_audit_record` | attachment upload and issue work-product routes | `task`
`artifacts`
`workProducts`
`workspace`
`audit`
`idempotency` | 4 | artifact/work-product reference and durable inspectability receipt; never binary bytes | Production upload/register composite and additive durable-reference receipt are unbound. | -| [`er` — Errors and critical rules](#behavior-group-er-errors-and-critical-rules) | runner/control plane + optional workspace/wake tools | `get_workspace_runtime`
`control_workspace_service`
`schedule_wake`
`inspect_operation_result` | `release_task`
`enforce_budget`
`persist_run`
`replay_run`
`reconcile_run` | workspace runtime, monitor/recovery, budget, run persistence/replay, release, and terminal services | `workspace`
`budget`
`run`
`wake`
`audit`
`idempotency`
`fault` | 9 | runtime/workspace/attention/run lifecycle, typed denials, replay facts, and terminal causality | Budget stop reasons and semantic denial/conflict receipts require additive v1 envelopes; inspect_operation_result remains scenario-only. | +| [`er` — Errors and critical rules](#behavior-group-er-errors-and-critical-rules) | runner/control plane + optional workspace/wake tools | `get_workspace_runtime`
`control_workspace_service`
`get_workspace`
`list_workspaces`
`select_workspace`
`prepare_repository`
`schedule_wake`
`inspect_operation_result` | `release_task`
`enforce_budget`
`persist_run`
`replay_run`
`reconcile_run` | workspace runtime, monitor/recovery, budget, run persistence/replay, release, and terminal services | `workspace`
`budget`
`run`
`wake`
`audit`
`idempotency`
`fault` | 9 | runtime/workspace/attention/run lifecycle, typed denials, replay facts, and terminal causality | Budget stop reasons and semantic denial/conflict receipts require additive v1 envelopes; inspect_operation_result remains scenario-only. Task workspace inspection, discovery, selection and repository preparation use live authenticated service contracts; the legacy scenarios do not claim coverage for these operations. | | [`rf` — Reference files](#behavior-group-rf-reference-files) | always instruction tools + optional domain tools + test-only escape hatch | `list_cases`
`upsert_case`
`list_routines`
`manage_routine`
`create_skill`
`update_skill`
`list_company_skills`
`sync_company_skills`
`list_secret_metadata`
`read_secret_value`
`export_company`
`administer_company`
`generic_api_request`
`search_api`
`call_api`
`create_project`
`read_agent_instructions`
`update_agent_instructions`
`get_agent_instruction_history`
`restore_agent_instructions`
`submit_complaint`
`submit_suggestion` | `append_audit_record` | agent instruction revisions, project, case, routine, company-skill, secret, portability, and administration services, and run-attributed agent commentary | `company`
`cases`
`routines`
`skills`
`secrets`
`audit`
`fault` | 22 | bounded domain projections, redacted broker receipts, company diffs, and audit references | Instruction read/update/history/restore require the live canonical revision service, current responsible-user permissions, and pinned base revisions for writes. Their service, authority, and product E2E tests are separate from the legacy mock scenarios. Project and skill creation and API tools require the live server authority; generic_api_request is test-only and the remaining domain operations are scenario-only. Broad administer_company is deferred and cannot claim product coverage. Production API and paired dedicated-tool regressions are recorded separately in paperclip-evals/evals/runner-api-tools; the legacy scenario count is not evidence of that coverage. Feedback actions are live-only: shared persistence, authority, API, and actual Codex smoke tests cover them; the legacy scenario count does not claim feedback coverage. | | [`mh` — Multi-hop](#behavior-group-mh-multi-hop) | composed semantic operations + control-plane continuation | `create_task`
`set_dependencies`
`request_human_input`
`request_approval`
`register_deliverable` | `route_wake`
`reconcile_run` | delegation, dependency, interaction, approval, artifact, and terminal orchestration services | `task`
`blockers`
`interactions`
`approvals`
`artifacts`
`wake`
`run`
`audit` | 4 | correlated operation receipts, state diffs, attention hops, work assessment, status decision, and terminal outcome | No generic transaction tool is allowed; shared mock/real conformance must prove each composed effect. | | [`rs` — Restraint and no-call](#behavior-group-rs-restraint-and-no-call) | policy/exposure layer | `answer_status_question`
`read_secret_value`
`generic_api_request` | `enforce_budget` | task-mode, secret-broker, test-scope, pause, and budget policy checks | `actor`
`task`
`budget`
`secrets`
`audit`
`fault` | 3 | absence of forbidden effects plus typed policy denial/redaction receipts when a call is attempted | Typed redaction/authorization receipts need additive v1 evidence; generic_api_request is never a product fallback. | @@ -469,10 +473,10 @@ Current responsibility-based paths are normative. Numbered `phase-*` or mileston ### Catalog split and deliberate replacement - Scenario/eval catalog: **40** operations. -- Live dispatcher catalog: **45** operations. -- Shared: **28**; union/canonical authority: **57**. +- Live dispatcher catalog: **49** operations. +- Shared: **28**; union/canonical authority: **61**. - Scenario-only: `administer_company`, `export_company`, `inspect_operation_result`, `list_cases`, `list_company_skills`, `list_goals`, `list_routines`, `list_secret_metadata`, `manage_routine`, `read_secret_value`, `sync_company_skills`, `upsert_case`. -- Live-only: `call_api`, `create_project`, `get_agent`, `get_agent_instruction_history`, `get_approval`, `get_approval_context`, `hire_agent`, `list_project_repositories`, `read_agent_instructions`, `restore_agent_instructions`, `schedule_wake`, `search_api`, `set_task_monitor`, `set_task_title`, `submit_complaint`, `submit_suggestion`, `update_agent_instructions`. +- Live-only: `call_api`, `create_project`, `get_agent`, `get_agent_instruction_history`, `get_approval`, `get_approval_context`, `get_workspace`, `hire_agent`, `list_project_repositories`, `list_workspaces`, `prepare_repository`, `read_agent_instructions`, `restore_agent_instructions`, `schedule_wake`, `search_api`, `select_workspace`, `set_task_monitor`, `set_task_title`, `submit_complaint`, `submit_suggestion`, `update_agent_instructions`. - The generated provider contract contains exactly the live catalog; the canonical union remains the migration authority until all scenario-only operations are either implemented, deferred, or removed by an explicit reconciliation decision. - `generic_api_request` stays exported only for controlled tests and cannot be cited as real-surface, mock-parity, or PRP product coverage. diff --git a/packages/paperclip-runner/src/catalog/reconciliation.test.ts b/packages/paperclip-runner/src/catalog/reconciliation.test.ts index 923fc036b33..89483b7cce8 100644 --- a/packages/paperclip-runner/src/catalog/reconciliation.test.ts +++ b/packages/paperclip-runner/src/catalog/reconciliation.test.ts @@ -20,9 +20,9 @@ describe("canonical semantic-catalog reconciliation authority", () => { it("pins the reconciled op-set relationship between the two catalogs", () => { const summary = capabilityCatalogReconciliation(); expect(summary.scenarioCount).toBe(40); - expect(summary.liveCount).toBe(45); + expect(summary.liveCount).toBe(49); expect(summary.sharedCount).toBe(28); - expect(summary.unionCount).toBe(57); + expect(summary.unionCount).toBe(61); // Any operation added to or removed from either catalog without a // reconciliation decision changes these exact sets and fails the gate. expect(summary.liveOnly).toEqual([ @@ -32,12 +32,16 @@ describe("canonical semantic-catalog reconciliation authority", () => { "get_agent_instruction_history", "get_approval", "get_approval_context", + "get_workspace", "hire_agent", "list_project_repositories", + "list_workspaces", + "prepare_repository", "read_agent_instructions", "restore_agent_instructions", "schedule_wake", "search_api", + "select_workspace", "set_task_monitor", "set_task_title", "submit_complaint", @@ -61,7 +65,7 @@ describe("canonical semantic-catalog reconciliation authority", () => { }); it("is the single source both catalogs derive their operation set from", () => { - expect(CAPABILITY_CANONICAL_OPERATIONS).toHaveLength(57); + expect(CAPABILITY_CANONICAL_OPERATIONS).toHaveLength(61); const canonicalIds = new Set(CAPABILITY_CANONICAL_OPERATIONS.map((operation) => operation.operationId)); // Neither catalog may contain an operation absent from the canonical source. for (const tool of SCENARIO_CATALOG) expect(canonicalIds.has(tool.operationId)).toBe(true); @@ -95,7 +99,7 @@ describe("canonical semantic-catalog reconciliation authority", () => { }); it("names placement, claims, task modes, side-effect class, idempotency, redaction, mock mapping, real binding status, and PRP evidence for every operation", () => { - expect(CAPABILITY_CANONICAL_CATALOG).toHaveLength(57); + expect(CAPABILITY_CANONICAL_CATALOG).toHaveLength(61); for (const operation of CAPABILITY_CANONICAL_CATALOG) { expect(operation.placement).toMatch(/^(always|optional)_agent_tool$/); expect(Array.isArray(operation.requiredClaims)).toBe(true); @@ -121,7 +125,7 @@ describe("canonical semantic-catalog reconciliation authority", () => { it("classifies real binding status so generic_api_request is never product coverage", () => { const summary = capabilityCatalogReconciliation(); expect(summary.byRealBindingStatus).toEqual({ - live_codex: 44, + live_codex: 48, scenario_mock: 12, test_only: 1, }); diff --git a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts index 7692309b236..c4a7102e16c 100644 --- a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts +++ b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts @@ -69,7 +69,7 @@ describe("semantic action catalog", () => { (action) => action.operationId, ); - expect(operationIds).toHaveLength(37); + expect(operationIds).toHaveLength(41); expect(operationIds).toContain("set_task_monitor"); expect(new Set(operationIds).size).toBe(operationIds.length); expect(operationIds).not.toContain("generic_api_request"); diff --git a/packages/paperclip-runner/src/catalog/semantic-action-catalog.ts b/packages/paperclip-runner/src/catalog/semantic-action-catalog.ts index 4c77c08fecb..b8cccc6869e 100644 --- a/packages/paperclip-runner/src/catalog/semantic-action-catalog.ts +++ b/packages/paperclip-runner/src/catalog/semantic-action-catalog.ts @@ -1,3 +1,7 @@ +import { getWorkspaceAction } from "../protocol-actions/get-workspace.js"; +import { listWorkspacesAction } from "../protocol-actions/list-workspaces.js"; +import { selectWorkspaceAction } from "../protocol-actions/select-workspace.js"; +import { prepareRepositoryAction } from "../protocol-actions/prepare-repository.js"; import { setTaskMonitorInputSchema } from "../protocol-actions/set-task-monitor.js"; import { listProjectsDescription, listProjectsInputSchema } from "../protocol-actions/list-projects.js"; import { setTaskTitleAction } from "../protocol-actions/set-task-title.js"; @@ -471,6 +475,10 @@ const descriptors: readonly PaperclipSemanticActionDescriptor[] = [ ), outputSchema: operationReceipt, }), + descriptor({ ...getWorkspaceAction.live.descriptor, placement: "optional" }), + descriptor({ ...listWorkspacesAction.live.descriptor, placement: "optional" }), + descriptor({ ...selectWorkspaceAction.live.descriptor, placement: "optional" }), + descriptor({ ...prepareRepositoryAction.live.descriptor, placement: "optional" }), descriptor({ operationId: "list_projects", title: "List projects", diff --git a/packages/paperclip-runner/src/catalog/semantic-action-types.ts b/packages/paperclip-runner/src/catalog/semantic-action-types.ts index 5f2f1bfef07..46665159522 100644 --- a/packages/paperclip-runner/src/catalog/semantic-action-types.ts +++ b/packages/paperclip-runner/src/catalog/semantic-action-types.ts @@ -29,6 +29,10 @@ export type PaperclipSemanticActionId = | "set_dependencies" | "create_skill" | "update_skill" + | "get_workspace" + | "list_workspaces" + | "select_workspace" + | "prepare_repository" | "create_project" | "list_project_repositories" | "list_projects" @@ -61,6 +65,7 @@ export interface PaperclipJsonSchema { readonly additionalProperties?: boolean | PaperclipJsonSchema; readonly items?: PaperclipJsonSchema; readonly enum?: readonly PaperclipJsonValue[]; + readonly const?: PaperclipJsonValue; readonly oneOf?: readonly PaperclipJsonSchema[]; readonly anyOf?: readonly PaperclipJsonSchema[]; readonly minimum?: number; diff --git a/packages/paperclip-runner/src/drivers/acpx/permission-policy.test.ts b/packages/paperclip-runner/src/drivers/acpx/permission-policy.test.ts index 85241d43444..833c17bd667 100644 --- a/packages/paperclip-runner/src/drivers/acpx/permission-policy.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/permission-policy.test.ts @@ -53,6 +53,16 @@ describe("ACPX permission policy", () => { }); }); + it.each(["approve-reads", "approve-paperclip"] as const)("requires repository acquisition approval in %s", (mode) => { + const tools = ["prepare_repository", "paperclip__prepare_repository", "mcp__paperclip__prepare_repository"] + .map(name => ({ name, annotations: { readOnlyHint: true, effect: "read" } })); + expect(claudePaperclipPermissionRules(tools, mode)).toEqual([]); + expect(decideAcpxPermission("claude", mode, { + inferredKind: "read", + raw: { toolCall: { name: "mcp__paperclip__prepare_repository" } }, + })).toBe("delegate"); + }); + it.each([ ["approve-all", "execute", "allow_once"], ["approve-reads", "read", "delegate"], diff --git a/packages/paperclip-runner/src/drivers/acpx/permission-policy.ts b/packages/paperclip-runner/src/drivers/acpx/permission-policy.ts index 63076db5ea1..576ff3ce1db 100644 --- a/packages/paperclip-runner/src/drivers/acpx/permission-policy.ts +++ b/packages/paperclip-runner/src/drivers/acpx/permission-policy.ts @@ -19,12 +19,11 @@ export class AcpxApprovalRequiredError extends Error { // Review additions explicitly: a newly catalogued mutation must not inherit // automatic provider permission. These operations implement the bounded plan, // task, and handoff workflow; governance decisions, generic APIs, and workspace -// controls deliberately stay outside it. Asking a human does not approve work. const AUTOMATIC_PAPERCLIP_WORKFLOW_ACTIONS = new Set([ "report_progress", "answer_status_question", "write_document", "request_human_input", "register_deliverable", "finish_task", "block_task", "set_task_title", "request_review", "create_task", "reassign_task", "set_dependencies", - "create_project", "request_approval", + "create_project", "select_workspace", "request_approval", ]); /** Exact SDK rules for the run's runner-owned Paperclip MCP connection. */ diff --git a/packages/paperclip-runner/src/eval/workflow-evals.test.ts b/packages/paperclip-runner/src/eval/workflow-evals.test.ts index c90118fe819..b9a377c318d 100644 --- a/packages/paperclip-runner/src/eval/workflow-evals.test.ts +++ b/packages/paperclip-runner/src/eval/workflow-evals.test.ts @@ -466,13 +466,13 @@ describe("workflow reports and stress traceability", () => { candidateFailures: 36, }); expect(report.coverage).toMatchObject({ - canonicalOperations: 57, + canonicalOperations: 61, capabilityCases: 106, workflows: 12, stressFindings: 44, stressExclusions: 1, }); - expect(report.coverage.operations).toHaveLength(57); + expect(report.coverage.operations).toHaveLength(61); expect(report.coverage.composedWorkflows).toHaveLength(12); expect( report.coverage.operations.find( @@ -480,7 +480,7 @@ describe("workflow reports and stress traceability", () => { )?.workflowIds.length, ).toBeGreaterThan(0); expect(renderRunnerWorkflowMarkdown(report)).toContain( - "57 operations · 106 capability cases · 12 workflows", + "61 operations · 106 capability cases · 12 workflows", ); expect(renderRunnerWorkflowJUnit(report)).toContain( 'tests="36" failures="36" skipped="0"', diff --git a/packages/paperclip-runner/src/generated/capability-contract.ts b/packages/paperclip-runner/src/generated/capability-contract.ts index de3cbd9f935..a679623e7c2 100644 --- a/packages/paperclip-runner/src/generated/capability-contract.ts +++ b/packages/paperclip-runner/src/generated/capability-contract.ts @@ -6,9 +6,9 @@ export type CapabilityPrimaryDisposition = | "optional_agent_tool"; export const capabilityInventoryCounts = { - "skillReferenceCapabilities": 162, + "skillReferenceCapabilities": 164, "evalCases": 106, - "normativeRows": 268, + "normativeRows": 270, "legacyMcpAliases": 42 } as const; diff --git a/packages/paperclip-runner/src/protocol-actions/get-workspace.ts b/packages/paperclip-runner/src/protocol-actions/get-workspace.ts new file mode 100644 index 00000000000..d4d4812f3af --- /dev/null +++ b/packages/paperclip-runner/src/protocol-actions/get-workspace.ts @@ -0,0 +1,72 @@ +export const getWorkspaceAction = { + "id": "get_workspace", + "canonical": { + "operationId": "get_workspace", + "surfaces": [ + "live" + ], + "placement": "optional_agent_tool", + "optionalGroup": "workspace_runtime", + "requiredClaims": [], + "taskModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "sideEffectClass": "read", + "idempotency": "none", + "disabledByDefault": false, + "realBindingStatus": "live_codex", + "realServiceBinding": "PaperclipRunnerToolAuthority", + "prpEvidence": "Authenticated task workspace routes and durable selection/repository receipts.", + "prpBindingStatus": "bound", + "legacyAliases": [] + }, + "documentation": { + "title": "Inspect task workspace", + "description": "Inspect current task files, repositories, pending selection, binding revision, and preparation capabilities. AGENT_HOME is personal instructions and memory; task outputs belong in the workspace.", + "note": null + }, + "examples": { + "call": { + "operationId": "get_workspace", + "input": {} + }, + "success": { + "ok": true, + "operationId": "get_workspace", + "result": {} + } + }, + "live": { + "order": 80, + "descriptor": { + "schema": "paperclip.semantic-tool.v1", + "operationId": "get_workspace", + "version": 1, + "title": "Inspect task workspace", + "description": "Inspect current task files, repositories, pending selection, binding revision, and preparation capabilities. AGENT_HOME is personal instructions and memory; task outputs belong in the workspace.", + "effect": "read", + "requiredClaims": [], + "allowedModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "inputSchema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "additionalProperties": true + }, + "exposure": "optional" + } + }, + "scenario": null +} as const; diff --git a/packages/paperclip-runner/src/protocol-actions/index.ts b/packages/paperclip-runner/src/protocol-actions/index.ts index 1c5b3083016..f21b890392c 100644 --- a/packages/paperclip-runner/src/protocol-actions/index.ts +++ b/packages/paperclip-runner/src/protocol-actions/index.ts @@ -1,3 +1,7 @@ +import { getWorkspaceAction } from "./get-workspace.js"; +import { listWorkspacesAction } from "./list-workspaces.js"; +import { selectWorkspaceAction } from "./select-workspace.js"; +import { prepareRepositoryAction } from "./prepare-repository.js"; import { setTaskMonitorAction } from "./set-task-monitor.js"; import { setTaskTitleAction } from "./set-task-title.js"; import { submitComplaintAction, submitSuggestionAction } from "./submit-agent-commentary.js"; @@ -57,6 +61,11 @@ import { writeDocumentAction } from "./write-document.js"; import { deepFreezeProtocolAction } from "./freeze.js"; export const PAPERCLIP_PROTOCOL_ACTIONS = deepFreezeProtocolAction([ + getWorkspaceAction, + listWorkspacesAction, + selectWorkspaceAction, + prepareRepositoryAction, + setTaskTitleAction, setTaskMonitorAction, submitComplaintAction, diff --git a/packages/paperclip-runner/src/protocol-actions/list-workspaces.ts b/packages/paperclip-runner/src/protocol-actions/list-workspaces.ts new file mode 100644 index 00000000000..c8c0f16d12e --- /dev/null +++ b/packages/paperclip-runner/src/protocol-actions/list-workspaces.ts @@ -0,0 +1,75 @@ +export const listWorkspacesAction = { + "id": "list_workspaces", + "canonical": { + "operationId": "list_workspaces", + "surfaces": [ + "live" + ], + "placement": "optional_agent_tool", + "optionalGroup": "workspace_runtime", + "requiredClaims": [], + "taskModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "sideEffectClass": "read", + "idempotency": "none", + "disabledByDefault": false, + "realBindingStatus": "live_codex", + "realServiceBinding": "PaperclipRunnerToolAuthority", + "prpEvidence": "Authenticated task workspace routes and durable selection/repository receipts.", + "prpBindingStatus": "bound", + "legacyAliases": [] + }, + "documentation": { + "title": "List available workspaces", + "description": "List authorized reusable workspaces. A project is optional; choosing a workspace does not assign a project.", + "note": null + }, + "examples": { + "call": { + "operationId": "list_workspaces", + "input": {} + }, + "success": { + "ok": true, + "operationId": "list_workspaces", + "result": [] + } + }, + "live": { + "order": 81, + "descriptor": { + "schema": "paperclip.semantic-tool.v1", + "operationId": "list_workspaces", + "version": 1, + "title": "List available workspaces", + "description": "List authorized reusable workspaces. A project is optional; choosing a workspace does not assign a project.", + "effect": "read", + "requiredClaims": [], + "allowedModes": [ + "standard", + "ask", + "planning", + "skill_test" + ], + "inputSchema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + }, + "outputSchema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "exposure": "optional" + } + }, + "scenario": null +} as const; diff --git a/packages/paperclip-runner/src/protocol-actions/prepare-repository.ts b/packages/paperclip-runner/src/protocol-actions/prepare-repository.ts new file mode 100644 index 00000000000..d0570ece72b --- /dev/null +++ b/packages/paperclip-runner/src/protocol-actions/prepare-repository.ts @@ -0,0 +1,122 @@ +export const prepareRepositoryAction = { + "id": "prepare_repository", + "canonical": { + "operationId": "prepare_repository", + "surfaces": [ + "live" + ], + "placement": "optional_agent_tool", + "optionalGroup": "workspace_runtime", + "requiredClaims": [], + "taskModes": [ + "standard", + "skill_test" + ], + "sideEffectClass": "write", + "idempotency": "required", + "disabledByDefault": false, + "realBindingStatus": "live_codex", + "realServiceBinding": "PaperclipRunnerToolAuthority", + "prpEvidence": "Authenticated task workspace routes and durable selection/repository receipts.", + "prpBindingStatus": "bound", + "legacyAliases": [] + }, + "documentation": { + "title": "Prepare task repository", + "description": "Request an authorized repository inside task files at the next normal admission, without creating a project. Discover repository IDs first. Does not interrupt or wake the current run. Reuse requestKey; a conflicting ref never resets existing work.", + "note": null + }, + "examples": { + "call": { + "operationId": "prepare_repository", + "input": { "repository": { "kind": "catalog", "id": "123" }, "requestKey": "example" } + }, + "success": { + "ok": true, + "operationId": "prepare_repository", + "result": {} + } + }, + "live": { + "order": 83, + "descriptor": { + "schema": "paperclip.semantic-tool.v1", + "operationId": "prepare_repository", + "version": 1, + "title": "Prepare task repository", + "description": "Request an authorized repository inside task files at the next normal admission, without creating a project. Discover repository IDs first. Does not interrupt or wake the current run. Reuse requestKey; a conflicting ref never resets existing work.", + "effect": "write", + "requiredClaims": [], + "allowedModes": [ + "standard", + "skill_test" + ], + "inputSchema": { + "type": "object", + "properties": { + "repository": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "const": "catalog" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "Authorized ID from list_project_repositories." + } + }, + "required": [ + "kind", + "id" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "const": "url" + }, + "url": { + "type": "string", + "minLength": 1, + "description": "Existing HTTPS GitHub repository URL; uncataloged URLs use public anonymous access only." + } + }, + "required": [ + "kind", + "url" + ], + "additionalProperties": false + } + ] + }, + "ref": { + "type": "string", + "minLength": 1, + "description": "Optional Git branch, tag, or commit. Default HEAD is pinned once." + }, + "requestKey": { + "type": "string", + "minLength": 1, + "description": "Stable retry key." + } + }, + "required": [ + "repository", + "requestKey" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "additionalProperties": true + }, + "exposure": "optional" + } + }, + "scenario": null +} as const; diff --git a/packages/paperclip-runner/src/protocol-actions/select-workspace.ts b/packages/paperclip-runner/src/protocol-actions/select-workspace.ts new file mode 100644 index 00000000000..2596f49dc84 --- /dev/null +++ b/packages/paperclip-runner/src/protocol-actions/select-workspace.ts @@ -0,0 +1,141 @@ +export const selectWorkspaceAction = { + "id": "select_workspace", + "canonical": { + "operationId": "select_workspace", + "surfaces": [ + "live" + ], + "placement": "optional_agent_tool", + "optionalGroup": "workspace_runtime", + "requiredClaims": [], + "taskModes": [ + "standard", + "skill_test" + ], + "sideEffectClass": "write", + "idempotency": "required", + "disabledByDefault": false, + "realBindingStatus": "live_codex", + "realServiceBinding": "PaperclipRunnerToolAuthority", + "prpEvidence": "Authenticated task workspace routes and durable selection/repository receipts.", + "prpBindingStatus": "bound", + "legacyAliases": [] + }, + "documentation": { + "title": "Select task workspace", + "description": "Request a different task workspace at the next normal admission. Does not interrupt or wake the current run. Inspect the binding revision first and reuse requestKey on retries; existing files stay in their current folder.", + "note": null + }, + "examples": { + "call": { + "operationId": "select_workspace", + "input": { "selection": { "kind": "task_directory" }, "expectedBindingRevision": 0, "requestKey": "example" } + }, + "success": { + "ok": true, + "operationId": "select_workspace", + "result": {} + } + }, + "live": { + "order": 82, + "descriptor": { + "schema": "paperclip.semantic-tool.v1", + "operationId": "select_workspace", + "version": 1, + "title": "Select task workspace", + "description": "Request a different task workspace at the next normal admission. Does not interrupt or wake the current run. Inspect the binding revision first and reuse requestKey on retries; existing files stay in their current folder.", + "effect": "write", + "requiredClaims": [], + "allowedModes": [ + "standard", + "skill_test" + ], + "inputSchema": { + "type": "object", + "properties": { + "selection": { + "oneOf": [ + { + "type": "object", + "properties": { + "kind": { + "const": "task_directory" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "const": "existing" + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "description": "Existing authorized workspace ID." + } + }, + "required": [ + "kind", + "workspaceId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "const": "configured_source" + }, + "projectWorkspaceId": { + "type": "string", + "minLength": 1, + "description": "Configured source workspace ID." + }, + "mode": { + "enum": [ + "shared", + "managed_isolated" + ] + } + }, + "required": [ + "kind", + "projectWorkspaceId", + "mode" + ], + "additionalProperties": false + } + ] + }, + "expectedBindingRevision": { + "type": "integer", + "minimum": 0 + }, + "requestKey": { + "type": "string", + "minLength": 1, + "description": "Stable retry key." + } + }, + "required": [ + "selection", + "expectedBindingRevision", + "requestKey" + ], + "additionalProperties": false + }, + "outputSchema": { + "type": "object", + "additionalProperties": true + }, + "exposure": "optional" + } + }, + "scenario": null +} as const; diff --git a/packages/paperclip-runner/src/semantic-tools/discovery.ts b/packages/paperclip-runner/src/semantic-tools/discovery.ts index 8877d2c765e..ab1e50c1920 100644 --- a/packages/paperclip-runner/src/semantic-tools/discovery.ts +++ b/packages/paperclip-runner/src/semantic-tools/discovery.ts @@ -34,7 +34,7 @@ const NAMESPACE: Readonly> = Objec update_agent_instructions: "agent_instructions", get_agent_instruction_history: "agent_instructions", restore_agent_instructions: "agent_instructions", - create_skill: "skills", update_skill: "skills", create_task: "delegation", create_project: "projects", list_project_repositories: "projects", list_projects: "projects", + create_skill: "skills", update_skill: "skills", create_task: "delegation", get_workspace: "workspace", list_workspaces: "workspace", select_workspace: "workspace", prepare_repository: "workspace", create_project: "projects", list_project_repositories: "projects", list_projects: "projects", set_dependencies: "delegation", reassign_task: "delegation", list_approvals: "governance", get_approval: "governance", get_approval_context: "governance", request_approval: "governance", decide_approval: "governance", comment_on_approval: "governance", @@ -48,7 +48,7 @@ export const CAPABILITY_DISCOVERY_NAMESPACES = Object.freeze([ { name: "discovery", description: "Find company tasks and agents." }, { name: "delegation", description: "Create bounded child work and task dependencies." }, { name: "governance", description: "Read, request, discuss, and decide approvals." }, - { name: "workspace", description: "Inspect and control active-task workspace services." }, + { name: "workspace", description: "Inspect task files, prepare repositories, select workspaces, and control workspace services." }, { name: "continuation", description: "Schedule a bounded continuation wake." }, { name: "feedback", description: "Submit internal complaints and improvement suggestions." }, ]); diff --git a/packages/paperclip-runner/src/semantic-tools/dispatcher.ts b/packages/paperclip-runner/src/semantic-tools/dispatcher.ts index ad58bb77b0a..2ef34a1ce49 100644 --- a/packages/paperclip-runner/src/semantic-tools/dispatcher.ts +++ b/packages/paperclip-runner/src/semantic-tools/dispatcher.ts @@ -188,6 +188,10 @@ export class CapabilitySemanticDispatcher { // it must neither advertise nor accept them merely for lacking claims. denyOperations: [...new Set([ ...(scenario.denyOperations ?? []), + "get_workspace" as const, + "list_workspaces" as const, + "select_workspace" as const, + "prepare_repository" as const, "create_project" as const, "list_project_repositories" as const, "list_projects" as const, diff --git a/packages/paperclip-runner/src/semantic-tools/paperclip-discovery.ts b/packages/paperclip-runner/src/semantic-tools/paperclip-discovery.ts index 491ac22e731..a5661946a49 100644 --- a/packages/paperclip-runner/src/semantic-tools/paperclip-discovery.ts +++ b/packages/paperclip-runner/src/semantic-tools/paperclip-discovery.ts @@ -39,7 +39,7 @@ const NAMESPACE: Readonly> = get_workspace_runtime: "workspace", control_workspace_service: "workspace", set_dependencies: "delegation", reassign_task: "delegation", - create_skill: "skills", update_skill: "skills", create_task: "delegation", create_project: "projects", list_project_repositories: "projects", list_projects: "projects", + create_skill: "skills", update_skill: "skills", create_task: "delegation", get_workspace: "workspace", list_workspaces: "workspace", select_workspace: "workspace", prepare_repository: "workspace", create_project: "projects", list_project_repositories: "projects", list_projects: "projects", request_approval: "governance", decide_approval: "governance", comment_on_approval: "governance", diff --git a/packages/paperclip-runner/src/semantic-tools/semantic-tools.test.ts b/packages/paperclip-runner/src/semantic-tools/semantic-tools.test.ts index 7dbc66243f6..5702bc3fa39 100644 --- a/packages/paperclip-runner/src/semantic-tools/semantic-tools.test.ts +++ b/packages/paperclip-runner/src/semantic-tools/semantic-tools.test.ts @@ -113,7 +113,7 @@ describe("Capability semantic catalog and authorization", () => { it("publishes a stable narrow catalog without credentials or control-plane-owned tools", () => { const names = CAPABILITY_SEMANTIC_TOOL_CATALOG.map((tool) => tool.operationId); expect(new Set(names).size).toBe(names.length); - expect(names).toHaveLength(45); + expect(names).toHaveLength(49); expect(names).toContain("set_task_monitor"); expect(names).toEqual(expect.arrayContaining(["submit_complaint", "submit_suggestion"])); expect(names).toContain("get_task_context"); @@ -187,7 +187,7 @@ describe("Capability semantic catalog and authorization", () => { expect(found.operations).toEqual([]); expect(JSON.stringify(found.operations)).not.toMatch(/create_task|approval|secret|administer_company/); const before = adapter.snapshot().revision; - for (const operationId of ["create_project", "list_project_repositories", "list_projects"] as const) { + for (const operationId of ["create_project", "list_project_repositories", "list_projects", "get_workspace", "list_workspaces", "select_workspace", "prepare_repository"] as const) { expect(dispatcher.listTools(OPEN.identity.runId).map((tool) => tool.name)).not.toContain(operationId); expect(await dispatcher.dispatch({ runId: OPEN.identity.runId, callId: `unbound-${operationId}`, operationId, diff --git a/packages/paperclip-runner/src/semantic-tools/types.ts b/packages/paperclip-runner/src/semantic-tools/types.ts index 28a5b1988de..9c34932943c 100644 --- a/packages/paperclip-runner/src/semantic-tools/types.ts +++ b/packages/paperclip-runner/src/semantic-tools/types.ts @@ -49,6 +49,10 @@ export type CapabilitySemanticOperationId = | "set_dependencies" | "create_skill" | "update_skill" + | "get_workspace" + | "list_workspaces" + | "select_workspace" + | "prepare_repository" | "create_project" | "list_project_repositories" | "list_projects" diff --git a/packages/plugins/sdk/src/types.ts b/packages/plugins/sdk/src/types.ts index 3378d248c4d..b1e2980315b 100644 --- a/packages/plugins/sdk/src/types.ts +++ b/packages/plugins/sdk/src/types.ts @@ -408,8 +408,7 @@ export interface PluginExecutionWorkspaceMetadata { id: string; /** UUID of the owning company. */ companyId: string; - /** UUID of the parent project. */ - projectId: string; + projectId: string | null; /** UUID of the backing project workspace, when present. */ projectWorkspaceId: string | null; /** Absolute filesystem path to the workspace when locally realized. */ diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index b41b34b0744..352ee970915 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -2873,4 +2873,9 @@ export { AGENT_LIFECYCLE_STATES, type AgentLifecycleState, type AgentLifecycleOp export type { AgentLifecycleRequest, AgentLifecycleResult } from "./types/agent-lifecycle.js"; +export * from "./types/task-workspace.js"; +export * from "./validators/task-workspace.js"; + +export { prepareWorkspaceRepositorySchema, type PrepareWorkspaceRepository } from "./validators/workspace-repository.js"; + export * from "./fast-response.js"; diff --git a/packages/shared/src/types/chat-channels.ts b/packages/shared/src/types/chat-channels.ts index 5ebcda06a2b..42cdb367382 100644 --- a/packages/shared/src/types/chat-channels.ts +++ b/packages/shared/src/types/chat-channels.ts @@ -1,3 +1,10 @@ +import type { TaskWorkspaceSelection } from "./task-workspace.js"; + +export interface ChatExecutionDefaults { + projectId?: string | null; + workspace?: TaskWorkspaceSelection | null; +} + /** Provider-neutral contracts for Paperclip's native external chat subsystem. */ export const CHAT_PROVIDERS = [ "slack", @@ -244,6 +251,7 @@ export type ChannelPublicationMode = "automatic" | "explicit"; export type ExternalMessageExecutionPolicy = "restricted" | "agent"; export interface ChatEndpoint { + executionDefaults?: ChatExecutionDefaults | null; /** Additional presentation instructions captured only for newly created tasks. */ communicationInstructions?: string; id: string; @@ -283,6 +291,7 @@ export interface ChatEndpoint { } export interface ChatEndpointResource { + executionDefaults?: ChatExecutionDefaults | null; id: string; companyId: string; endpointId: string; @@ -513,6 +522,7 @@ export interface CreateChatEndpointInput { } export interface UpdateChatEndpointInput { + executionDefaults?: ChatExecutionDefaults | null; slackSetupMethod?: "managed" | "automatic" | "manual" | "existing"; slackApp?: SlackAppConfiguration; communicationInstructions?: string; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 754fc0252c7..9e3cb1ed93a 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1101,3 +1101,5 @@ export type { AgentInstructionCandidate } from "./agent.js"; export * from "./skill-source.js"; export * from "./agent-lifecycle.js"; + +export * from "./task-workspace.js"; diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 87880b28075..4d187a833bf 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -1,3 +1,4 @@ +import type { TaskWorkspaceIntent, TaskWorkspacePendingSelection } from "./task-workspace.js"; import type { ExecutionProjection, ExecutionBlocker } from "./execution-projection.js"; import type { IssueCommentAuthorType, @@ -891,6 +892,9 @@ export interface Issue { monitorAttemptCount?: number; monitorNotes?: string | null; monitorScheduledBy?: IssueMonitorScheduledBy | null; + workspaceBindingRevision?: number; + workspaceSelection?: TaskWorkspaceIntent | null; + workspacePendingSelection?: TaskWorkspacePendingSelection | null; executionWorkspaceId: string | null; executionWorkspacePreference: string | null; executionWorkspaceSettings: IssueExecutionWorkspaceSettings | null; diff --git a/packages/shared/src/types/task-workspace.ts b/packages/shared/src/types/task-workspace.ts new file mode 100644 index 00000000000..0938a8dca28 --- /dev/null +++ b/packages/shared/src/types/task-workspace.ts @@ -0,0 +1,16 @@ +export type TaskWorkspaceSelection = + | { kind: "task_directory" } + | { kind: "existing"; workspaceId: string } + | { kind: "configured_source"; projectWorkspaceId: string; mode: "shared" | "managed_isolated" }; +export type TaskWorkspaceIntent = { + version: 1; + request?: { key: string; expectedBindingRevision: number }; + selection: TaskWorkspaceSelection; + source: "explicit" | "parent" | "channel" | "project" | "operator_cwd" | "task_default" | "legacy"; +}; +export type TaskWorkspacePendingSelection = { + version: 1; + requestKey: string; + expectedBindingRevision: number; + intent: TaskWorkspaceIntent; +}; diff --git a/packages/shared/src/types/workspace-runtime.ts b/packages/shared/src/types/workspace-runtime.ts index 25a8dbf6549..b6a3fe75524 100644 --- a/packages/shared/src/types/workspace-runtime.ts +++ b/packages/shared/src/types/workspace-runtime.ts @@ -229,9 +229,9 @@ export interface WorkspaceOverviewItem { kind: "execution_workspace"; workspaceId: string; workspaceName: string; - projectId: string; - projectUrlKey: string; - projectName: string; + projectId: string | null; + projectUrlKey: string | null; + projectName: string | null; mode: ExecutionWorkspaceSummary["mode"]; strategyType: ExecutionWorkspaceStrategyType; cwd: string | null; @@ -262,7 +262,7 @@ export interface WorkspaceOverviewResponse { export interface ExecutionWorkspace { id: string; companyId: string; - projectId: string; + projectId: string | null; projectWorkspaceId: string | null; sourceIssueId: string | null; mode: Exclude | "adapter_managed" | "cloud_sandbox"; diff --git a/packages/shared/src/validators/chat-channels.ts b/packages/shared/src/validators/chat-channels.ts index 53b263462f0..9d0adf5ce89 100644 --- a/packages/shared/src/validators/chat-channels.ts +++ b/packages/shared/src/validators/chat-channels.ts @@ -1,5 +1,11 @@ import { z } from "zod"; import { multilineTextSchema } from "./text.js"; +import { taskWorkspaceSelectionSchema } from "./task-workspace.js"; + +export const chatExecutionDefaultsSchema = z.object({ + projectId: z.string().uuid().nullable().optional(), + workspace: taskWorkspaceSelectionSchema.nullable().optional(), +}).strict(); import { CHAT_CONCURRENCY_POLICIES, CHAT_DELIVERY_STATES, @@ -89,6 +95,7 @@ export const createChatEndpointSchema = z export const updateChatEndpointSchema = z .object({ + executionDefaults: chatExecutionDefaultsSchema.nullable().optional(), communicationInstructions: multilineTextSchema.pipe(z.string().trim().max(4000)).optional(), slackApp: slackAppConfigurationSchema.optional(), slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(), @@ -148,7 +155,8 @@ export const replaceChatEndpointResourcesSchema = z z .object({ id: z.string().uuid(), - enabled: z.boolean(), + enabled: z.boolean().optional(), + executionDefaults: chatExecutionDefaultsSchema.nullable().optional(), }) .strict(), ) diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index 47cf59aeb8a..e7b3197a550 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -996,3 +996,5 @@ export { restoreAgentInstructionSchema } from "./agent.js"; export * from "./skill-source.js"; export * from "./agent-commentary.js"; +export * from "./task-workspace.js"; +export * from "./workspace-repository.js"; diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 29f6f07376c..8fddad793ba 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -1,3 +1,4 @@ +import { taskWorkspaceSelectionSchema } from "./task-workspace.js"; import { z } from "zod"; import { ISSUE_EXECUTION_DECISION_OUTCOMES, @@ -737,6 +738,7 @@ const createIssueBaseSchema = z.object({ .enum(ISSUE_EXECUTION_WORKSPACE_PREFERENCES) .optional() .nullable(), + workspaceSelection: taskWorkspaceSelectionSchema.optional(), executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema .optional() .nullable(), @@ -884,6 +886,7 @@ const issueCommentAttachmentIdsSchema = z export const updateIssueSchema = objectWithoutDefaults( createIssueBaseSchema.omit({ + workspaceSelection: true, createdByUserId: true, responsibleUserId: true, watchdog: true, diff --git a/packages/shared/src/validators/task-workspace.ts b/packages/shared/src/validators/task-workspace.ts new file mode 100644 index 00000000000..c2b8fe7ad16 --- /dev/null +++ b/packages/shared/src/validators/task-workspace.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +export const taskWorkspaceSelectionSchema = z.discriminatedUnion("kind", [ + z.object({ kind: z.literal("task_directory") }).strict(), + z.object({ kind: z.literal("existing"), workspaceId: z.string().uuid() }).strict(), + z.object({ kind: z.literal("configured_source"), projectWorkspaceId: z.string().uuid(), mode: z.enum(["shared", "managed_isolated"]) }).strict(), +]); +export const taskWorkspaceIntentSchema = z.object({ + version: z.literal(1), request: z.object({ key: z.string().min(1).max(200), expectedBindingRevision: z.number().int().nonnegative() }).strict().optional(), selection: taskWorkspaceSelectionSchema, + source: z.enum(["explicit", "parent", "channel", "project", "operator_cwd", "task_default", "legacy"]), +}).strict(); +export const selectTaskWorkspaceSchema = z.object({ + selection: taskWorkspaceSelectionSchema, + expectedBindingRevision: z.number().int().nonnegative(), + requestKey: z.string().trim().min(1).max(200), +}).strict(); diff --git a/packages/shared/src/validators/workspace-repository.ts b/packages/shared/src/validators/workspace-repository.ts new file mode 100644 index 00000000000..e753942ce57 --- /dev/null +++ b/packages/shared/src/validators/workspace-repository.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const prepareWorkspaceRepositorySchema = z.object({ + repository: z.discriminatedUnion("kind", [ + z.object({ kind: z.literal("catalog"), id: z.string().min(1).max(255) }).strict(), + z.object({ kind: z.literal("url"), url: z.string().url().max(2048) }).strict(), + ]), + ref: z.string().min(1).max(255).regex(/^[A-Za-z0-9][A-Za-z0-9._\/-]*$/).refine(value => !value.includes("..") && !value.includes("//"), "Invalid Git ref").optional(), + requestKey: z.string().min(1).max(240), +}).strict(); +export type PrepareWorkspaceRepository = z.infer; diff --git a/server/src/__tests__/agent-directory-probe.test.ts b/server/src/__tests__/agent-directory-probe.test.ts index 01a7b4b0236..d3f3ebce588 100644 --- a/server/src/__tests__/agent-directory-probe.test.ts +++ b/server/src/__tests__/agent-directory-probe.test.ts @@ -91,7 +91,7 @@ describe("stable live agent directory observation", () => { const baseline = await captureDirectorySnapshot(root); runtime = await prepareAdapterExecutionTargetRuntime({ target, runId: "probe-transfer", adapterKey: "agent-files", workspaceLocalDir: root, workspaceRemoteDir: remote, syncWorkspace: true, workspaceBaseline: baseline, - workspaceGitSnapshot: null, workspaceFileMode: "all", workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); + workspaceGitSnapshot: null, workspaceRepositories: [], workspaceFileMode: "all", workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); expect(fs.readdirSync(`${remote}/.paperclip-runtime`)).toEqual(["agent-files"]); expect(fs.readdirSync(`${remote}/.paperclip-runtime/agent-files`)).toEqual([]); execFileSync(process.execPath, ["-e", agentDirectoryTransferCleanupProgram, remote], { env: {}, timeout: 10_000 }); @@ -99,6 +99,49 @@ describe("stable live agent directory observation", () => { expect(probeAgentDirectory(remote).digest).toBe(agentDirectoryBaselineDigest(baseline)); } finally { await runtime?.cleanupWorkspaceSnapshot?.(); fs.rmSync(remote, { recursive: true, force: true }); } }); + it.each([false, true])("round-trips the raw agent-directory repository namespace without discovery (recovering=%s)", async recovering => { + const remoteCwd = `${root}-remote`; + const agentHome = join(remoteCwd, ".paperclip-runtime", "agent-files", "agent", "original-run"); + const scratch = join(remoteCwd, ".paperclip-runtime", "paperclip-runner", "agent-file-transfers", "agent", "original-run"); + const rawFiles = { + ".paperclip-repositories/notes.txt": "Ordinary agent memory\n", + ".paperclip-repositories/archive/.git/config": "Raw nested configuration\n", + ".paperclip-repositories/archive/.git/hooks/post-commit": "Raw nested hook\n", + ".git/HEAD": "Raw root Git metadata\n", + "node_modules/memory.bin": "Ordinary cache directory bytes\n", + }; + for (const [relative, contents] of Object.entries(rawFiles)) { + fs.mkdirSync(join(root, relative, ".."), { recursive: true }); + fs.writeFileSync(join(root, relative), contents); + } + fs.mkdirSync(agentHome, { recursive: true }); + if (recovering) fs.cpSync(root, agentHome, { recursive: true }); + const target = { kind: "remote" as const, transport: "sandbox" as const, remoteCwd, + runner: { execute: async (input: Parameters[0]) => ({ + stdout: execFileSync(input.command, input.args ?? [], { cwd: input.cwd, input: input.stdin, encoding: "utf8", + env: { PATH: process.env.PATH, ...input.env }, timeout: input.timeoutMs, maxBuffer: 32 * 1024 * 1024 }), + stderr: "", exitCode: 0, signal: null, timedOut: false, pid: null, startedAt: new Date().toISOString(), + }) } }; + let runtime: Awaited> | undefined; + try { + runtime = await prepareAdapterExecutionTargetRuntime({ target, runId: "plain-agent-directory", adapterKey: "agent-files", + workspaceLocalDir: root, workspaceRemoteDir: agentHome, runtimeRootDir: scratch, syncWorkspace: true, + workspaceInboundMode: recovering ? "adopt_remote" : undefined, workspaceBaseline: await captureDirectorySnapshot(root), + workspaceGitSnapshot: null, workspaceRepositories: [], workspaceFileMode: "all", + workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); + for (const [relative, contents] of Object.entries(rawFiles)) expect(fs.readFileSync(join(agentHome, relative), "utf8")).toBe(contents); + expect(probeAgentDirectory(agentHome).digest).toBe(probeAgentDirectory(root).digest); + fs.writeFileSync(join(agentHome, ".paperclip-repositories/notes.txt"), "Updated memory\n"); + fs.writeFileSync(join(agentHome, ".paperclip-repositories/archive/.git/hooks/post-commit"), "Updated raw hook\n"); + fs.unlinkSync(join(agentHome, "notes/memory.bin")); + await runtime.restoreWorkspace(); + expect(fs.readFileSync(join(root, ".paperclip-repositories/notes.txt"), "utf8")).toBe("Updated memory\n"); + expect(fs.readFileSync(join(root, ".paperclip-repositories/archive/.git/hooks/post-commit"), "utf8")).toBe("Updated raw hook\n"); + expect(fs.existsSync(join(root, "notes/memory.bin"))).toBe(false); + expect(fs.readFileSync(join(root, ".git/HEAD"), "utf8")).toBe(rawFiles[".git/HEAD"]); + expect(probeAgentDirectory(agentHome).digest).toBe(probeAgentDirectory(root).digest); + } finally { await runtime?.cleanupWorkspaceSnapshot?.(); fs.rmSync(remoteCwd, { recursive: true, force: true }); } + }); it("keeps restart fallback transfer scratch outside the native memory directory", async () => { const remoteCwd = `${root}-remote`; const agentHome = join(remoteCwd, ".paperclip-runtime", "agent-files", "agent", "original-run"); @@ -119,7 +162,7 @@ describe("stable live agent directory observation", () => { runtime = await prepareAdapterExecutionTargetRuntime({ target, runId: "restart-transfer", adapterKey: "agent-files", workspaceLocalDir: root, workspaceRemoteDir: agentHome, runtimeRootDir: scratch, syncWorkspace: true, workspaceInboundMode: "adopt_remote", workspaceBaseline: baseline, - workspaceGitSnapshot: null, workspaceFileMode: "all", workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); + workspaceGitSnapshot: null, workspaceRepositories: [], workspaceFileMode: "all", workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); const memory = "0123456789abcdef0123456789abcdef\n"; fs.writeFileSync(join(agentHome, "notes", "memory.txt"), memory); await runtime.restoreWorkspace(); diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index 6893a548784..a550acedf8b 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -103,6 +103,8 @@ import { nativeRunFinalizations, environmentLeases, principalPermissionGrants, + projects, + projectWorkspaces, toolConnections, } from "@paperclipai/db"; import { defaultGitHubReviewPolicy, type ChatProvider } from "@paperclipai/shared"; @@ -4627,7 +4629,7 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { changes: { body: { from: "No mention" } }, }; await f.service.handleWebhook(f.endpoint.publicId, "github", signedGitHubWebhookRequest({ event, delivery: randomUUID(), payload, webhookSecret: f.webhookSecret })); - await expect.poll(async () => (await readChatConversationsByEndpointId(f.endpoint.id)).length).toBe(1); + await expect.poll(async () => (await readChatConversationsByEndpointId(f.endpoint.id)).length, { timeout: 10_000 }).toBe(1); const deliveries = await readChatDeliveriesByEndpointId(f.endpoint.id); expect(deliveries).toEqual(expect.arrayContaining([ expect.objectContaining({ eventKind: "message_updated" }), @@ -8113,6 +8115,137 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { expect((await readChatDeliveriesById(delivery.id))[0].state).toBe("filtered"); }); + it("snapshots channel workspace defaults on new tasks and preserves them on replies", async () => { + const fixture = await seedCompany(); + const { callbacks, endpoint, service } = await configuredSlackEndpoint(fixture); + await service.update(endpoint.id, { executionDefaults: { workspace: { kind: "task_directory" } } }, "owner-user"); + const thread = makeThread({ channelId: "C-WORKSPACE", id: "slack:C-WORKSPACE:8100.1", name: "workspace" }); + await deliverMessage({ callbacks, endpointId: endpoint.id, thread: thread.thread, + message: makeMessage({ id: "8100.1", text: "@maya make a report", mentioned: true }), trigger: "mention" }); + const conversations = () => db.select().from(chatConversations).where(eq(chatConversations.endpointId, endpoint.id)).orderBy(asc(chatConversations.createdAt)); + const [first] = await conversations(); + const [task] = await db.select().from(issues).where(eq(issues.id, first.issueId)); + expect(task.projectId).toBeNull(); + expect(task.workspaceSelection).toEqual({ version: 1, selection: { kind: "task_directory" }, source: "channel" }); + const [resource] = (await service.listResources(endpoint.id)).filter(row => row.providerResourceId === "C-WORKSPACE"); + expect(resource).toBeDefined(); + await service.replaceResources(endpoint.id, [{ id: resource.id, executionDefaults: { workspace: null } }], "owner-user"); + expect((await service.listResources(endpoint.id)).find(row => row.id === resource.id)?.enabled).toBe(resource.enabled); + await service.update(endpoint.id, { executionDefaults: null }, "owner-user"); + await deliverMessage({ callbacks, endpointId: endpoint.id, thread: thread.thread, + message: makeMessage({ id: "8100.2", text: "Continue" }), trigger: "subscribed_message" }); + const [unchanged] = await db.select().from(issues).where(eq(issues.id, first.issueId)); + expect(unchanged.workspaceSelection).toEqual(task.workspaceSelection); + await service.update(endpoint.id, { executionDefaults: { workspace: { kind: "task_directory" } } }, "owner-user"); + const next = makeThread({ channelId: "C-WORKSPACE", id: "slack:C-WORKSPACE:8200.1", name: "workspace" }); + await deliverMessage({ callbacks, endpointId: endpoint.id, thread: next.thread, + message: makeMessage({ id: "8200.1", text: "@maya another report", mentioned: true }), trigger: "mention" }); + const rows = await conversations(); + expect(rows).toHaveLength(2); + const [second] = await db.select().from(issues).where(eq(issues.id, rows[1].issueId)); + expect(second.workspaceSelection).toBeNull(); + await expect(service.update(endpoint.id, { executionDefaults: { projectId: randomUUID() } }, "owner-user")).rejects.toMatchObject({ status: 404 }); + expect((await service.get(endpoint.id)).executionDefaults).toEqual({ workspace: { kind: "task_directory" } }); + }); + + it("checks both linked sender and agent assignment grants for protected organizational defaults", async () => { + const fixture = await seedCompany(); + const { callbacks, endpoint, service, wakeup } = await configuredSlackEndpoint(fixture, { linkedBoardUser: true }); + const [project] = await db.insert(projects).values({ companyId: fixture.companyId, name: "Protected task project", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const defaults = { projectId: project.id, workspace: { kind: "task_directory" as const } }; + await service.update(endpoint.id, { executionDefaults: defaults }, "owner-user"); + await db.insert(companyMemberships).values({ companyId: fixture.companyId, principalType: "agent", + principalId: fixture.assignedAgentId, status: "active", membershipRole: "member" }); + const send = async (id: string) => { + const thread = makeThread({ channelId: "C-ASSIGNMENT", id: `slack:C-ASSIGNMENT:${id}`, name: "assignment" }); + await deliverMessage({ callbacks, endpointId: endpoint.id, thread: thread.thread, + message: makeMessage({ id, text: "@maya create a report", mentioned: true, userId: "UBOARD" }), trigger: "mention" }); + }; + const tasks = () => db.select().from(issues).where(eq(issues.companyId, fixture.companyId)); + await expect(send("8310.1")).rejects.toMatchObject({ status: 403, message: expect.stringContaining("protected") }); + expect(await tasks()).toHaveLength(0); + expect(wakeup).not.toHaveBeenCalled(); + const [senderGrant] = await db.insert(principalPermissionGrants).values({ companyId: fixture.companyId, + principalType: "user", principalId: "owner-user", permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }).returning(); + await expect(send("8310.2")).rejects.toMatchObject({ status: 403, message: expect.stringContaining("protected") }); + expect(await tasks()).toHaveLength(0); + expect(wakeup).not.toHaveBeenCalled(); + await db.insert(principalPermissionGrants).values({ companyId: fixture.companyId, + principalType: "agent", principalId: fixture.assignedAgentId, permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }); + await send("8310.3"); + expect(await tasks()).toEqual([expect.objectContaining({ projectId: project.id, + assigneeAgentId: fixture.assignedAgentId, + workspaceSelection: { version: 1, selection: { kind: "task_directory" }, source: "channel" } })]); + expect(wakeup).toHaveBeenCalledTimes(1); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, senderGrant.id)); + await expect(send("8310.4")).rejects.toMatchObject({ status: 403, message: expect.stringContaining("protected") }); + expect(await tasks()).toHaveLength(1); + expect(wakeup).toHaveBeenCalledTimes(1); + expect(await db.select().from(chatConversations).where(eq(chatConversations.endpointId, endpoint.id))).toHaveLength(1); + }); + + it("checks protected organizational defaults against the assignee locked at creation", async () => { + const fixture = await seedCompany(); + let endpointId = ""; + const { callbacks, endpoint, service, wakeup } = await configuredSlackEndpoint(fixture, { + linkedBoardUser: true, + reachAuthorizationBarrier: async () => { + await db.update(chatEndpoints).set({ assignedAgentId: fixture.replacementAgentId }).where(eq(chatEndpoints.id, endpointId)); + }, + }); + endpointId = endpoint.id; + const [project] = await db.insert(projects).values({ companyId: fixture.companyId, name: "Protected reassignment", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await service.update(endpoint.id, { executionDefaults: { projectId: project.id, workspace: { kind: "task_directory" } } }, "owner-user"); + await db.insert(companyMemberships).values({ companyId: fixture.companyId, principalType: "agent", + principalId: fixture.assignedAgentId, status: "active", membershipRole: "member" }); + await db.insert(principalPermissionGrants).values([ + { companyId: fixture.companyId, principalType: "user", principalId: "owner-user", permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }, + { companyId: fixture.companyId, principalType: "agent", principalId: fixture.assignedAgentId, permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }, + ]); + const thread = makeThread({ channelId: "C-REASSIGNED", id: "slack:C-REASSIGNED:8320.1", name: "reassigned" }); + await expect(deliverMessage({ callbacks, endpointId: endpoint.id, thread: thread.thread, + message: makeMessage({ id: "8320.1", text: "@maya create a report", mentioned: true, userId: "UBOARD" }), trigger: "mention" })) + .rejects.toMatchObject({ status: 403, message: expect.stringContaining("protected") }); + expect(await db.select().from(issues).where(eq(issues.companyId, fixture.companyId))).toHaveLength(0); + expect(wakeup).not.toHaveBeenCalled(); + }); + + it("authorizes protected channel workspace defaults for the endpoint's assigned agent", async () => { + const fixture = await seedCompany(); + const { callbacks, endpoint, service } = await configuredSlackEndpoint(fixture, { linkedBoardUser: true }); + const [project] = await db.insert(projects).values({ companyId: fixture.companyId, name: "Protected chat source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: fixture.companyId, projectId: project.id, + name: "Source", cwd: `/tmp/chat-source-${fixture.companyId}` }).returning(); + const defaults = { workspace: { kind: "configured_source" as const, projectWorkspaceId: source.id, mode: "shared" as const } }; + await expect(service.update(endpoint.id, { executionDefaults: defaults }, "owner-user")).rejects.toMatchObject({ status: 403 }); + await db.insert(companyMemberships).values({ companyId: fixture.companyId, principalType: "agent", + principalId: fixture.assignedAgentId, status: "active", membershipRole: "member" }); + await db.insert(principalPermissionGrants).values([ + { companyId: fixture.companyId, principalType: "user", principalId: "owner-user", permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }, + { companyId: fixture.companyId, principalType: "agent", principalId: fixture.assignedAgentId, permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: fixture.assignedAgentId } }, + ]); + await service.update(endpoint.id, { executionDefaults: defaults }, "owner-user"); + const thread = makeThread({ channelId: "C-PROTECTED", id: "slack:C-PROTECTED:8300.1", name: "protected" }); + await deliverMessage({ callbacks, endpointId: endpoint.id, thread: thread.thread, + message: makeMessage({ id: "8300.1", text: "@maya update these files", mentioned: true, userId: "UBOARD" }), trigger: "mention" }); + const [conversation] = await db.select().from(chatConversations).where(eq(chatConversations.endpointId, endpoint.id)); + expect(conversation).toBeDefined(); + const [task] = await db.select().from(issues).where(eq(issues.id, conversation.issueId)); + expect(task).toMatchObject({ assigneeAgentId: fixture.assignedAgentId, projectId: null, projectWorkspaceId: source.id }); + const [resource] = (await service.listResources(endpoint.id)).filter(row => row.providerResourceId === "C-PROTECTED"); + await service.replaceResources(endpoint.id, [{ id: resource.id, executionDefaults: defaults }], "owner-user"); + expect((await service.listResources(endpoint.id)).find(row => row.id === resource.id)?.executionDefaults).toEqual(defaults); + }); + it("captures initial Slack communication guidance once per task, ignoring forged message configuration", async () => { const fixture = await seedCompany(); const { callbacks, endpoint, service } = await configuredSlackEndpoint(fixture); @@ -64924,7 +65057,10 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { } }); - it("migrates Telegram basic-group reach and topic tasks to the replacement supergroup", async () => { + it.each([ + ...["unset", "inherit", "clear", "configured"].flatMap((defaultsMode) => + [false, true].map((existingDestination) => ({ defaultsMode, existingDestination }))), + ])("migrates Telegram basic-group reach, topic tasks and $defaultsMode defaults (existing destination: $existingDestination)", async ({ defaultsMode, existingDestination }) => { const fixture = await seedCompany(); const { callbacks, endpoint, service, wakeup } = await configuredTelegramEndpoint(fixture); @@ -64932,6 +65068,17 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { .update(chatEndpoints) .set({ status: "active" }) .where(eq(chatEndpoints.id, endpoint.id)); + const [defaultProject, selectedProject] = await db.insert(projects).values([ + { companyId: fixture.companyId, name: "Endpoint default project" }, + { companyId: fixture.companyId, name: "Migrated resource project" }, + ]).returning(); + await service.update(endpoint.id, { executionDefaults: { + projectId: defaultProject.id, workspace: { kind: "task_directory" }, + } }, "owner-user"); + const executionDefaults = defaultsMode === "unset" ? null + : defaultsMode === "inherit" ? {} + : defaultsMode === "clear" ? { projectId: null, workspace: null } + : { projectId: selectedProject.id, workspace: { kind: "task_directory" as const } }; const previousChatId = "-5546433913"; const migratedChatId = "-1004415501660"; const topicId = 77; @@ -64945,8 +65092,14 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { label: "Telegram migration group", availability: "available", enabled: true, + executionDefaults, }) .returning(); + if (existingDestination) await db.insert(chatEndpointResources).values({ + companyId: fixture.companyId, endpointId: endpoint.id, type: "chat", + providerResourceId: migratedChatId, label: "Existing destination", availability: "available", + executionDefaults: { projectId: selectedProject.id, workspace: null }, + }); const previousTopic = makeThread({ channelId: `telegram:${previousChatId}`, id: `telegram:${previousChatId}:${topicId}`, @@ -65005,6 +65158,7 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { }), expect.objectContaining({ providerResourceId: migratedChatId, + executionDefaults, label: "Telegram migration group", availability: "available", enabled: true, @@ -65059,6 +65213,20 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { { body: "Continue by replying after the group migration" }, ]); expect(wakeup).toHaveBeenCalledTimes(2); + + const newTopic = makeThread({ channelId: `telegram:${migratedChatId}`, + id: `telegram:${migratedChatId}:${topicId + 1}`, name: "New migrated topic" }); + await deliverMessage({ callbacks, endpointId: endpoint.id, provider: "telegram", thread: newTopic.thread, + message: makeMessage({ id: `${migratedChatId}:43`, raw: { message_id: 43, message_thread_id: topicId + 1 }, + text: "Start a new topic after migration", mentioned: true, userId: "telegram-migration-user" }), trigger: "mention" }); + const newConversation = (await service.listConversations(endpoint.id)).find(row => row.id !== beforeMigration.id); + expect(newConversation).toBeDefined(); + const [newTask] = await readIssuesById(newConversation!.issueId); + expect(newTask.projectId).toBe(defaultsMode === "clear" ? null + : defaultsMode === "configured" ? selectedProject.id : defaultProject.id); + expect(newTask.workspaceSelection).toEqual(defaultsMode === "clear" ? null + : { version: 1, selection: { kind: "task_directory" }, source: "channel" }); + expect(wakeup).toHaveBeenCalledTimes(3); }); it("enforces Telegram group reach and privacy-mode addressing before retaining message content", async () => { diff --git a/server/src/__tests__/chat-execution-defaults.test.ts b/server/src/__tests__/chat-execution-defaults.test.ts new file mode 100644 index 00000000000..0f04a1499f4 --- /dev/null +++ b/server/src/__tests__/chat-execution-defaults.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { chatExecutionDefaultsSchema, updateChatEndpointSchema, replaceChatEndpointResourcesSchema } from "@paperclipai/shared"; +import { resolveChatExecutionDefaults } from "../services/chat-execution-defaults.js"; + +const projectId = "11f01381-578d-4bb7-9de4-57e6d9678313"; +const workspaceId = "13f01381-578d-4bb7-9de4-57e6d9678313"; + +describe("channel execution defaults", () => { + it("leaves unconfigured channels on common task defaults", () => { + expect(resolveChatExecutionDefaults(null, null)).toEqual({}); + }); + it("inherits omitted properties and clears explicit null independently", () => { + expect(resolveChatExecutionDefaults({ projectId, workspace: { kind: "existing", workspaceId } }, { projectId: null })) + .toEqual({ projectId: null, workspace: { kind: "existing", workspaceId } }); + expect(resolveChatExecutionDefaults({ projectId, workspace: { kind: "existing", workspaceId } }, { workspace: null })) + .toEqual({ projectId, workspace: null }); + }); + it("resource task directories override endpoint shared choices", () => { + expect(resolveChatExecutionDefaults({ workspace: { kind: "existing", workspaceId } }, { workspace: { kind: "task_directory" } })) + .toEqual({ workspace: { kind: "task_directory" } }); + }); + it("rejects unknown fields, paths and commands rather than granting filesystem authority", () => { + for (const input of [{ cwd: "/tmp" }, { workspace: { kind: "task_directory", command: "echo hello" } }, { projectId: "not-a-uuid" }]) { + expect(chatExecutionDefaultsSchema.safeParse(input).success).toBe(false); + } + }); + it("accepts defaults through both endpoint and resource management contracts", () => { + expect(updateChatEndpointSchema.parse({ executionDefaults: { projectId } })).toEqual({ executionDefaults: { projectId } }); + expect(replaceChatEndpointResourcesSchema.parse({ resources: [{ id: workspaceId, enabled: true, executionDefaults: { workspace: null } }] }).resources[0].executionDefaults) + .toEqual({ workspace: null }); + }); +}); diff --git a/server/src/__tests__/chat-project-tools.test.ts b/server/src/__tests__/chat-project-tools.test.ts index b75f5ed6609..157cff77c02 100644 --- a/server/src/__tests__/chat-project-tools.test.ts +++ b/server/src/__tests__/chat-project-tools.test.ts @@ -246,7 +246,7 @@ const support = await getEmbeddedPostgresTestSupport(); await expect(call(f, "create_task", { title: "Exact scope is not a subtree", idempotencyKey: "exact-child" })).rejects.toThrow(/outside.*boundary/); }); - it("authorizes workspace-derived projects before creating children of a projectless root", async () => { + it("authorizes workspace sources without assigning projects to children of a projectless root", async () => { const f = await server.fixture({ disableWakeOnDemand: true }); const settings = instanceSettingsService(server.db); const previous = await settings.getExperimental(); @@ -254,7 +254,7 @@ const support = await getEmbeddedPostgresTestSupport(); try { await server.db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, f.issueId)); const [executionWorkspace] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, - projectId: f.projectId, projectWorkspaceId: f.projectWorkspaceId, mode: "shared", strategyType: "project_primary", name: "Outside root" }).returning(); + projectId: f.projectId, projectWorkspaceId: f.projectWorkspaceId, mode: "shared_workspace", strategyType: "project_primary", name: "Outside root" }).returning(); const boundary = { mode: "low_trust_review", companyId: f.companyId, rootIssueId: f.issueId }; await server.db.update(agents).set({ permissions: { trustPreset: "low_trust_review", authorizationPolicy: { trustBoundary: boundary } } }).where(eq(agents.id, f.agentId)); const token = createLocalAgentJwt(f.agentId, f.companyId, "paperclip_runner", f.runId, f.responsibleUserId)!; @@ -279,6 +279,8 @@ const support = await getEmbeddedPostgresTestSupport(); } expect(await server.db.select({ id: issues.id }).from(issues).where(eq(issues.companyId, f.companyId))).toEqual(before); + await server.db.update(issues).set({ executionWorkspaceId: executionWorkspace.id }).where(eq(issues.id, f.issueId)); + await expect(call(f, "create_task", { title: "Inherited source cannot escape", idempotencyKey: "inherited-source-denied", status: "backlog" })).rejects.toThrow(/outside.*boundary/i); await server.db.update(agents).set({ permissions: { trustPreset: "low_trust_review", authorizationPolicy: { trustBoundary: { ...boundary, projectIds: [f.projectId] }, } } }).where(eq(agents.id, f.agentId)); @@ -287,7 +289,11 @@ const support = await getEmbeddedPostgresTestSupport(); if (path.endsWith("/children") && selection.inheritExecutionWorkspaceFromIssueId) continue; const response = await create(path, selection); expect(response.status, JSON.stringify(response.body)).toBe(201); - expect(response.body.projectId).toBe(f.projectId); + expect(response.body.projectId).toBe(selection.inheritExecutionWorkspaceFromIssueId ? f.projectId : null); + if (selection.projectWorkspaceId) { + expect(response.body.projectWorkspaceId).toBe(selection.projectWorkspaceId); + expect(response.body.executionWorkspaceId).toBeNull(); + } expect(response.body.executionPolicy.authorizationPolicy.trustBoundary.projectIds).toEqual([f.projectId]); } } diff --git a/server/src/__tests__/durable-chat-wakeup.test.ts b/server/src/__tests__/durable-chat-wakeup.test.ts index f2f0f3f042b..3afa386bfef 100644 --- a/server/src/__tests__/durable-chat-wakeup.test.ts +++ b/server/src/__tests__/durable-chat-wakeup.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { randomUUID } from "node:crypto"; import { and, eq } from "drizzle-orm"; import { @@ -12,6 +13,8 @@ import { import { agents, companies, + authUsers, + companyMemberships, createDb, heartbeatRuns, agentWakeupRequests, @@ -87,7 +90,7 @@ describe("durable inbound chat scheduler receipts", () => { testedAt: new Date(0).toISOString(), }), }); - }, 30_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(() => { execute.mockClear(); for (const unregister of unregisterAuthorities.splice(0)) unregister(); @@ -111,6 +114,8 @@ describe("durable inbound chat scheduler receipts", () => { defaultResponsibleUserId: "board-user", requireBoardApprovalForNewAgents: false, }); + await db.insert(authUsers).values({ id: "board-user", name: "Fixture user", email: "board-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "board-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, diff --git a/server/src/__tests__/email-channels.integration.test.ts b/server/src/__tests__/email-channels.integration.test.ts index 781496206b9..bf297326a1c 100644 --- a/server/src/__tests__/email-channels.integration.test.ts +++ b/server/src/__tests__/email-channels.integration.test.ts @@ -512,6 +512,64 @@ describe("AgentMail durable email pipeline", () => { await db.select().from(issues).where(eq(issues.companyId, f.companyId)), ).toHaveLength(1); }); + it("snapshots email workspace defaults only when a conversation creates a task", async () => { + const f = await fixture(); + await db.update(chatEndpoints).set({ executionDefaults: { workspace: { kind: "task_directory" } } }).where(eq(chatEndpoints.id, f.endpointId)); + const first = f.message(); + await f.receive(first); + const tasks = () => db.select().from(issues).where(eq(issues.companyId, f.companyId)); + const [task] = await tasks(); + expect(task.workspaceSelection).toEqual({ version: 1, selection: { kind: "task_directory" }, source: "channel" }); + expect(task.projectId).toBeNull(); + await db.update(chatEndpoints).set({ executionDefaults: null }).where(eq(chatEndpoints.id, f.endpointId)); + await f.receive(f.message(randomUUID(), first.thread_id)); + expect((await tasks())[0].workspaceSelection).toEqual(task.workspaceSelection); + await f.receive(f.message()); + expect(await tasks()).toEqual(expect.arrayContaining([expect.objectContaining({ workspaceSelection: null })])); + }); + + it("checks sponsor and agent assignment grants for protected email organizational defaults", async () => { + const f = await fixture(); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected email tasks", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const fetcher = vi.mocked(f.fetcher); + const fetch = fetcher.getMockImplementation()!; + let changedDefaults = false; + fetcher.mockImplementation(async (...args) => { + if (!changedDefaults && String(args[0]).includes("/threads/")) { + changedDefaults = true; + await db.update(chatEndpoints).set({ executionDefaults: { projectId: project.id, workspace: { kind: "task_directory" } } }) + .where(eq(chatEndpoints.id, f.endpointId)); + } + return fetch(...args); + }); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "agent", + principalId: f.agentId, status: "active", membershipRole: "member" }); + const tasks = () => db.select().from(issues).where(eq(issues.companyId, f.companyId)); + await f.receive(f.message()); + expect(await tasks()).toHaveLength(0); + expect(f.wakeup).not.toHaveBeenCalled(); + const [sponsorGrant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, + principalType: "user", principalId: "email-board", permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: f.agentId } }).returning(); + await f.receive(f.message()); + expect(await tasks()).toHaveLength(0); + expect(f.wakeup).not.toHaveBeenCalled(); + await db.insert(principalPermissionGrants).values({ companyId: f.companyId, + principalType: "agent", principalId: f.agentId, permissionKey: "tasks:assign_scope", + scope: { projectId: project.id, assigneeAgentId: f.agentId } }); + await f.receive(f.message()); + expect(await tasks()).toEqual([expect.objectContaining({ projectId: project.id, + assigneeAgentId: f.agentId, + workspaceSelection: { version: 1, selection: { kind: "task_directory" }, source: "channel" } })]); + expect(f.wakeup).toHaveBeenCalledTimes(1); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, sponsorGrant.id)); + await f.receive(f.message()); + expect(await tasks()).toHaveLength(1); + expect(f.wakeup).toHaveBeenCalledTimes(1); + expect(await db.select().from(chatConversations).where(eq(chatConversations.endpointId, f.endpointId))).toHaveLength(1); + }); + it("deduplicates events/messages, keeps identical subjects separate, and never grants sender board identity", async () => { const f = await fixture(); const m = f.message(); diff --git a/server/src/__tests__/execution-workspace-reopen.test.ts b/server/src/__tests__/execution-workspace-reopen.test.ts index 9ff003a237c..98d0dee41b4 100644 --- a/server/src/__tests__/execution-workspace-reopen.test.ts +++ b/server/src/__tests__/execution-workspace-reopen.test.ts @@ -1,13 +1,20 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { execFileSync } from "node:child_process"; import { mkdirSync, writeFileSync } from "node:fs"; -import { mkdtemp, rm, stat } from "node:fs/promises"; +import { mkdtemp, rm, stat, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { randomUUID } from "node:crypto"; +import { pathToFileURL } from "node:url"; +import { assertExecutionWorkspaceRootReadAccess } from "../services/execution-workspace-repositories.js"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { activityLog, + agents, + authUsers, + companyMemberships, + issueAccessGrants, companies, createDb, executionWorkspaces, @@ -48,7 +55,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-reopen-"); db = createDb(tempDb.connectionString); - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { await db.delete(activityLog); @@ -56,7 +63,10 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { await db.delete(executionWorkspaces); await db.delete(projectWorkspaces); await db.delete(projects); + await db.delete(companyMemberships); + await db.delete(agents); await db.delete(companies); + await db.delete(authUsers); await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); }); @@ -250,6 +260,84 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { .then((rows) => rows[0] ?? null); } + describe.each(["configured", "builtin"] as const)("reopen %s host provision containment", kind => { + it.each(["task", "agent", "organization", "source", "same-source", "trusted", "explicit", "projectless", "nondefault", "foreign-source-task", "retained-missing", "retained-false", "retained-null"] as const)("honors persisted %s policy before running repository scripts", async restriction => { + const { companyId, projectId, projectWorkspaceId } = await seedCompanyProject(); + const directory = await makeExistingDir(); + const base = join(directory, "source"), target = join(directory, "target"), marker = join(directory, "host-provision-ran"); + initManagedGitRepo(base, "reopen-feature"); + const command = `printf 'host execution\n' > '${marker}'`; + mkdirSync(join(base, "scripts")); + writeFileSync(join(base, "scripts", "provision-worktree.sh"), command + "\n"); + const git = (...args: string[]) => execFileSync("git", args, { cwd: base, stdio: "ignore", + env: { ...process.env, GIT_AUTHOR_NAME: "Test", GIT_AUTHOR_EMAIL: "test@example.com", GIT_COMMITTER_NAME: "Test", GIT_COMMITTER_EMAIL: "test@example.com" } }); + git("add", "scripts"); git("commit", "-m", "Host provision fixture"); git("branch", "-f", "reopen-feature", "HEAD"); + await db.update(projectWorkspaces).set({ cwd: base }).where(eq(projectWorkspaces.id, projectWorkspaceId)); + const workspaceId = await seedClosedGitWorktreeWorkspace({ companyId, projectId, projectWorkspaceId, + cwd: target, repoUrl: pathToFileURL(base).href, branchName: "reopen-feature" }); + if (kind === "configured") await db.update(executionWorkspaces).set({ metadata: { + [EXECUTION_WORKSPACE_LIFECYCLE_GENERATION_METADATA_KEY]: 1, config: { provisionCommand: command }, + } }).where(eq(executionWorkspaces.id, workspaceId)); + const issueId = await seedIssue({ companyId, projectId, workspaceId, issueNumber: 4199 }); + let organizationId: string | null = restriction === "projectless" ? null : projectId; + if (restriction === "organization") { + organizationId = randomUUID(); + await db.insert(projects).values({ id: organizationId, companyId, name: "Organizational policy" }); + } + const policy = { trustPreset: "low_trust_review", authorizationPolicy: { trustPreset: "low_trust_review", + trustBoundary: { mode: "low_trust_review", companyId, rootIssueId: issueId, + projectIds: [...new Set([projectId, ...(organizationId ? [organizationId] : [])])] } } }; + const agentId = randomUUID(); + await db.insert(agents).values({ id: agentId, companyId, name: "Reopen assignee", + permissions: restriction === "agent" ? policy : {} }); + await db.update(issues).set({ projectId: organizationId, assigneeAgentId: agentId, + executionPolicy: restriction === "task" ? policy : null }).where(eq(issues.id, issueId)); + if (["source", "same-source", "organization"].includes(restriction)) { + await db.update(projects).set({ executionWorkspacePolicy: policy }) + .where(eq(projects.id, restriction === "organization" ? organizationId! : projectId)); + } + if (restriction === "explicit" || restriction.startsWith("retained-")) { + await db.update(issues).set({ workspaceSelection: { version: 1, source: "explicit", + selection: { kind: "configured_source", projectWorkspaceId, mode: "managed_isolated" } } }) + .where(eq(issues.id, issueId)); + } + if (restriction === "nondefault") { + await db.update(projectWorkspaces).set({ isPrimary: false }).where(eq(projectWorkspaces.id, projectWorkspaceId)); + await db.insert(projectWorkspaces).values({ companyId, projectId, name: "Earlier default", + sourceType: "local_path", cwd: base, isPrimary: true, createdAt: new Date(0) }); + } + if (restriction === "foreign-source-task") { + const sourceIssueId = await seedIssue({ companyId, projectId, workspaceId, issueNumber: 4198 }); + await db.update(executionWorkspaces).set({ sourceIssueId }).where(eq(executionWorkspaces.id, workspaceId)); + } + if (restriction.startsWith("retained-")) { + const workspace = await readWorkspace(workspaceId); + await db.update(executionWorkspaces).set({ metadata: { ...workspace?.metadata, + _controllerProvisionCopyOnly: true } }).where(eq(executionWorkspaces.id, workspaceId)); + await db.update(issues).set({ workspaceSelection: null }).where(eq(issues.id, issueId)); + const service = executionWorkspaceService(db); + await service.update(workspaceId, { metadata: restriction === "retained-null" ? null + : restriction === "retained-false" ? { _controllerProvisionCopyOnly: false } : {} }); + if (kind === "configured") await service.update(workspaceId, { metadata: { config: { provisionCommand: command } } }); + expect((await readWorkspace(workspaceId))?.metadata).toMatchObject({ _controllerProvisionCopyOnly: true }); + } + const result = await executionWorkspaceService(db).reopenClosedIsolatedExecutionWorkspaceForIssue({ + workspaceId, issue: { id: issueId, companyId, projectId: organizationId }, + actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, + }); + expect(result).toMatchObject({ ok: true, reopened: true }); + await expect(readFile(join(target, "README.md"), "utf8")).resolves.toBe("seed\n"); + if (restriction === "trusted") await expect(readFile(marker, "utf8")).resolves.toBe("host execution\n"); + else await expect(readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + expect(await readWorkspace(workspaceId)).toMatchObject({ status: "active", projectId, projectWorkspaceId }); + if (["explicit", "projectless", "nondefault", "foreign-source-task"].includes(restriction) || restriction.startsWith("retained-")) expect((await readWorkspace(workspaceId))?.metadata) + .toMatchObject({ _controllerProvisionCopyOnly: true }); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0]) + .toMatchObject({ projectId: organizationId, executionWorkspaceId: workspaceId, assigneeAgentId: agentId }); + }); + }); + it("reopens the archived row in place, keeps the issue link, and raises the generation", async () => { const { companyId, projectId, projectWorkspaceId } = await seedCompanyProject(); const cwd = await makeExistingDir(); @@ -261,6 +349,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(true); @@ -282,6 +371,22 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { expect(issueRow?.executionWorkspaceId).toBe(workspaceId); }); + it("reopens the persisted source after the task leaves its organizational project", async () => { + const { companyId, projectId, projectWorkspaceId } = await seedCompanyProject(); + const cwd = await makeExistingDir(); + const workspaceId = await seedClosedWorkspace({ companyId, projectId, projectWorkspaceId, cwd }); + const issueId = await seedIssue({ companyId, projectId, workspaceId, issueNumber: 4105 }); + await db.update(issues).set({ projectId: null }).where(eq(issues.id, issueId)); + const result = await executionWorkspaceService(db).reopenClosedIsolatedExecutionWorkspaceForIssue({ + workspaceId, + issue: { id: issueId, companyId, projectId: null }, + actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, + }); + expect(result.ok).toBe(true); + expect((await readWorkspace(workspaceId))?.projectId).toBe(projectId); + }); + it("keeps access for every issue that shares the reopened row", async () => { const { companyId, projectId, projectWorkspaceId } = await seedCompanyProject(); const cwd = await makeExistingDir(); @@ -294,6 +399,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: firstIssueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(true); @@ -324,6 +430,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(false); @@ -369,6 +476,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(true); @@ -389,6 +497,80 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { } }); + it.each([false, true, "revoked_during_provision"] as const)("guards a private local managed reopen before Git and retains privacy after provisioning failure=%s", async scenario => { + const provisionFails = scenario === true; + const revokeDuringProvision = scenario === "revoked_during_provision"; + const previousHome = process.env.PAPERCLIP_HOME; + const previousPrivacy = process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; + const home = await makeExistingDir(); + process.env.PAPERCLIP_HOME = home; + process.env.PAPERCLIP_ISSUE_PRIVACY_MODE = "enforce"; + try { + const { companyId, projectId, projectWorkspaceId } = await seedManagedCheckoutProject(); + const readerId = randomUUID(); + await db.insert(authUsers).values({ id: readerId, name: "Reader", email: `${readerId}@example.test`, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: readerId, status: "active", membershipRole: "member" }); + const authorizationActor = { type: "board" as const, source: "session" as const, userId: readerId, companyIds: [companyId], isInstanceAdmin: false }; + const source = join(home, "private-source"), target = join(home, "worktree-copy"); + initManagedGitRepo(source, "reopen-feature"); + writeFileSync(join(source, "private.txt"), "PRIVATE_REOPEN_BYTES"); + execFileSync("git", ["add", "private.txt"], { cwd: source }); + execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "private"], { cwd: source }); + execFileSync("git", ["branch", "-f", "reopen-feature", "HEAD"], { cwd: source }); + const repoUrl = pathToFileURL(source).href; + await db.update(projectWorkspaces).set({ repoUrl }).where(eq(projectWorkspaces.id, projectWorkspaceId)); + const privateIssueId = randomUUID(); + await db.insert(issues).values({ id: privateIssueId, companyId, title: "Private source", visibility: "private", status: "todo", responsibleUserId: "another-owner" }); + await db.insert(executionWorkspaces).values({ companyId, projectId: null, sourceIssueId: privateIssueId, name: "Private owner", mode: "shared_workspace", strategyType: "project_primary", cwd: source, providerType: "local_fs", status: "active" }); + const workspaceId = await seedClosedGitWorktreeWorkspace({ companyId, projectId, projectWorkspaceId, cwd: target, repoUrl, branchName: "reopen-feature" }); + const waitingProvision = `node -e 'const fs = require("node:fs"); fs.writeFileSync("dirty.txt", "DIRTY_REOPEN_BYTES"); fs.writeFileSync("provision-started", "ready"); const deadline = Date.now() + 15000; const timer = setInterval(() => { if (fs.existsSync("provision-continue")) { clearInterval(timer); process.exit(0); } if (Date.now() > deadline) process.exit(2); }, 20);'`; + if (provisionFails || revokeDuringProvision) await db.update(executionWorkspaces).set({ metadata: { [EXECUTION_WORKSPACE_LIFECYCLE_GENERATION_METADATA_KEY]: 1, config: { provisionCommand: provisionFails ? "exit 42" : waitingProvision } } }).where(eq(executionWorkspaces.id, workspaceId)); + const issueId = await seedIssue({ companyId, projectId, workspaceId, issueNumber: 4901 }); + if (revokeDuringProvision) await db.update(issues).set({ status: "done" }).where(eq(issues.id, issueId)); + const reopen = () => executionWorkspaceService(db).reopenClosedIsolatedExecutionWorkspaceForIssue({ workspaceId, issue: { id: issueId, companyId, projectId }, actor: { actorType: "user", agentId: null }, authorizationActor }); + expect(await reopen()).toMatchObject({ ok: false, code: "rebuild_failed" }); + await expect(stat(target)).rejects.toMatchObject({ code: "ENOENT" }); + const managed = resolveManagedProjectWorkspaceDir({ companyId, projectId, repoName: "private-source" }); + await expect(stat(managed)).rejects.toMatchObject({ code: "ENOENT" }); + await db.insert(issueAccessGrants).values({ issueId: privateIssueId, subjectType: "user", subjectId: readerId, source: "explicit" }); + const attempt = revokeDuringProvision ? reopen() : null; + if (attempt) { + try { + await vi.waitFor(async () => expect(await readFile(join(target, "provision-started"), "utf8")).toBe("ready"), { timeout: 10_000 }); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + } finally { + await writeFile(join(target, "provision-continue"), "resume"); + } + } + const results = attempt ? [await attempt] : provisionFails ? [await reopen()] : await Promise.all([reopen(), reopen()]); + expect(results.every(result => result.ok)).toBe(!provisionFails && !revokeDuringProvision); + if (!provisionFails && !revokeDuringProvision) expect(results.map(result => result.ok && result.reopened).sort()).toEqual([false, true]); + expect(await readFile(join(target, "private.txt"), "utf8")).toBe("PRIVATE_REOPEN_BYTES"); + if (revokeDuringProvision) { + expect(results[0]).toMatchObject({ ok: false, code: "rebuild_failed" }); + const reopened = await readWorkspace(workspaceId); + expect(reopened?.status).toBe("active"); + expect(metadataHasReopenPendingConsumption(reopened?.metadata)).toBe(false); + expect(await readFile(join(target, "dirty.txt"), "utf8")).toBe("DIRTY_REOPEN_BYTES"); + const activity = await db.select().from(activityLog).where(eq(activityLog.entityId, workspaceId)); + expect(activity.map(row => row.action)).toContain("execution_workspace.reopen_unconsumed"); + const archive = await executionWorkspaceService(db).archiveWorkspaceUnderLifecycleLock({ id: workspaceId, patch: {}, closedAt: new Date() }); + expect(archive?.outcome).toBe("archived"); + expect(await readFile(join(target, "dirty.txt"), "utf8")).toBe("DIRTY_REOPEN_BYTES"); + } else { + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId, cwd: target, actor: authorizationActor })).resolves.toBeUndefined(); + } + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, projectWorkspaceId)); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + await rm(source, { recursive: true, force: true }); + for (const cwd of [managed, target]) await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId, cwd, actor: authorizationActor })).rejects.toMatchObject({ status: 403 }); + expect(await readFile(join(target, "private.txt"), "utf8")).toBe("PRIVATE_REOPEN_BYTES"); + } finally { + if (previousHome === undefined) delete process.env.PAPERCLIP_HOME; else process.env.PAPERCLIP_HOME = previousHome; + if (previousPrivacy === undefined) delete process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; else process.env.PAPERCLIP_ISSUE_PRIVACY_MODE = previousPrivacy; + } + }, 30_000); + it("refuses to reopen a workspace in another company", async () => { const first = await seedCompanyProject(); const second = await seedCompanyProject(); @@ -406,6 +588,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { // The issue belongs to a different company than the workspace. issue: { id: randomUUID(), companyId: second.companyId, projectId: second.projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(false); @@ -434,6 +617,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(result.ok).toBe(true); @@ -497,6 +681,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(reopenResult.ok).toBe(true); if (!reopenResult.ok) throw new Error("reopen failed"); @@ -558,6 +743,7 @@ describeEmbeddedPostgres("reopen archived isolated execution workspace", () => { workspaceId, issue: { id: issueId, companyId, projectId }, actor: { agentId: null, actorType: "user" }, + authorizationActor: { type: "board", source: "local_implicit", isInstanceAdmin: true }, }); expect(reopenResult.ok).toBe(true); if (!reopenResult.ok) throw new Error("reopen failed"); diff --git a/server/src/__tests__/execution-workspaces-routes.test.ts b/server/src/__tests__/execution-workspaces-routes.test.ts index 650b3351a31..1090222bd9d 100644 --- a/server/src/__tests__/execution-workspaces-routes.test.ts +++ b/server/src/__tests__/execution-workspaces-routes.test.ts @@ -146,6 +146,7 @@ describe("execution workspace routes", () => { issueId: undefined, status: undefined, reuseEligible: true, + selectableForTask: false, }); expect(mockExecutionWorkspaceService.list).not.toHaveBeenCalled(); }); diff --git a/server/src/__tests__/execution-workspaces-service.test.ts b/server/src/__tests__/execution-workspaces-service.test.ts index 68c0ea20779..fee0730d7b9 100644 --- a/server/src/__tests__/execution-workspaces-service.test.ts +++ b/server/src/__tests__/execution-workspaces-service.test.ts @@ -1,3 +1,5 @@ +import * as localServiceSupervisor from "../services/local-service-supervisor.js"; +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import fs from "node:fs/promises"; @@ -264,7 +266,7 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { // tests further down. workspaceReaperCooldownDays: 0, }); - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { await db.delete(workspaceRuntimeServices); @@ -3598,6 +3600,9 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { ].join(" "); const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify(serverScript)}`; + const portOwner = vi.spyOn(localServiceSupervisor, "readLocalServicePortOwner").mockResolvedValue(null); + const adoption = vi.spyOn(localServiceSupervisor, "findAdoptableLocalService").mockResolvedValue(null); + const runtimeOutput: string[] = []; let startedServices: Awaited> = []; try { const startPromise = startRuntimeServicesForWorkspaceControl({ @@ -3644,9 +3649,11 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { }, }, adapterEnv: {}, + onLog: async (stream, chunk) => { runtimeOutput.push(`${stream}: ${chunk}`); }, }); - await Promise.race([ + try { + await Promise.race([ waitForPath(runtimeStartedMarker), startPromise.then( () => { @@ -3656,7 +3663,13 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { throw error; }, ), - ]); + ]); + } catch (error) { + const activity = await db.execute(sql`select state, wait_event_type, wait_event, + left(query, 240) as query from pg_stat_activity + where datname = current_database() and pid <> pg_backend_pid()`); + throw new Error(`Runtime activation did not reach its process marker. Output: ${runtimeOutput.join("") || ""}. Database activity: ${JSON.stringify(activity)}`, { cause: error }); + } const reconcileErrorPromise = svc.reconcileExecutionWorkspaceBranch(executionWorkspaceId, { mode: "override", @@ -3694,12 +3707,17 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { }, }); } finally { - await stopRuntimeServicesForExecutionWorkspace({ - db, - executionWorkspaceId, - workspaceCwd: worktreePath, - }); - await fs.rm(runtimeStartedMarker, { force: true }); + try { + await stopRuntimeServicesForExecutionWorkspace({ + db, + executionWorkspaceId, + workspaceCwd: worktreePath, + }); + await fs.rm(runtimeStartedMarker, { force: true }); + } finally { + portOwner.mockRestore(); + adoption.mockRestore(); + } } const [workspace] = await db diff --git a/server/src/__tests__/file-resources.test.ts b/server/src/__tests__/file-resources.test.ts index e1aea92c813..ae556531bab 100644 --- a/server/src/__tests__/file-resources.test.ts +++ b/server/src/__tests__/file-resources.test.ts @@ -1,4 +1,5 @@ import express from "express"; +import { createHash } from "node:crypto"; import type { Request } from "express"; import { execFile } from "node:child_process"; import fs from "node:fs/promises"; @@ -7,8 +8,9 @@ import path from "node:path"; import { promisify } from "node:util"; import request from "supertest"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { activityLog, agents, companies, createDb, executionWorkspaces, goals, issues, projects, projectWorkspaces, type Db } from "@paperclipai/db"; +import { activityLog, agents, companies, companyMemberships, createDb, executionWorkspaceRepositoryAccess, executionWorkspaceRepositoryRoots, executionWorkspaceRepositories, executionWorkspaces, goals, issueAccessGrants, issues, projects, projectAccessMembers, projectWorkspaces, type Db } from "@paperclipai/db"; import { eq } from "drizzle-orm"; +import { taskRepositoryRelativePath } from "../services/execution-workspace-repositories.js"; import { errorHandler } from "../middleware/index.js"; import { createFileResourceAvailabilityLimiter, @@ -86,6 +88,7 @@ async function seedGraph(db: Db, input: { { id: companyId, name: `Company ${suffix}`, issuePrefix: `F${prefixSuffix}` }, { id: otherCompanyId, name: `Other ${suffix}`, issuePrefix: `G${prefixSuffix}` }, ]); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "board-user", status: "active", membershipRole: "member" }); await db.insert(goals).values([ { id: goalId, companyId, title: "Goal", level: "company", status: "active" }, { id: otherGoalId, companyId: otherCompanyId, title: "Other goal", level: "company", status: "active" }, @@ -200,12 +203,14 @@ describeEmbeddedPostgres("workspace file resources", () => { let db: Db; beforeAll(async () => { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); tempDb = await startEmbeddedPostgresTestDatabase("paperclip-file-resources-"); db = createDb(tempDb.connectionString); }, 60_000); afterAll(async () => { await tempDb?.cleanup(); + vi.unstubAllEnvs(); }); it("withholds private task files and private cross-project targets", async () => { @@ -311,6 +316,282 @@ describeEmbeddedPostgres("workspace file resources", () => { expect(resolved.capabilities.preview).toBe(true); }); + it.each(["missing", "denied", "symlink loop", "overlong path"])("skips an unused %s configured alternative while keeping explicit reads strict", async alternative => { + const { root, projectRoot, executionRoot, targetProjectRoot } = await makeWorkspace(); + try { + const graph = await seedGraph(db, { projectRoot, executionRoot, targetProjectRoot }); + await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await fs.writeFile(path.join(executionRoot, "report.txt"), "CURRENT_TASK_REPORT"); + if (alternative === "missing" || alternative === "symlink loop") { + await fs.rm(projectRoot, { recursive: true }); + if (alternative === "symlink loop") await fs.symlink(projectRoot, projectRoot); + } else if (alternative === "overlong path") { + await db.update(projectWorkspaces).set({ cwd: path.join(projectRoot, "x".repeat(512)) }).where(eq(projectWorkspaces.id, graph.projectWorkspaceId)); + } else { + const [privateIssue] = await db.insert(issues).values({ companyId: graph.companyId, title: "Private alternative source", visibility: "private", responsibleUserId: "private-owner" }).returning(); + await db.insert(executionWorkspaces).values({ companyId: graph.companyId, sourceIssueId: privateIssue.id, name: "Private alternative", cwd: projectRoot, + mode: "shared_workspace", strategyType: "task_directory", providerType: "local_fs" }); + await fs.writeFile(path.join(projectRoot, "report.txt"), "PRIVATE_ALTERNATIVE_BYTES"); + } + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + const base = `/api/issues/${graph.issueId}/file-resources`; + const listed = await request(app).get(`${base}/list`).expect(200); + expect(listed.body.items).toEqual(expect.arrayContaining([expect.objectContaining({ relativePath: "report.txt" })])); + await request(app).get(`${base}/resolve`).query({ path: "report.txt" }).expect(200); + const preview = await request(app).get(`${base}/content`).query({ path: "report.txt" }).expect(200); + expect(preview.body.content.data).toBe("CURRENT_TASK_REPORT"); + const download = await request(app).get(`${base}/content`).query({ path: "report.txt", download: true }).expect(200); + expect(download.text).toBe("CURRENT_TASK_REPORT"); + const explicit = { projectId: graph.projectId, workspaceId: graph.projectWorkspaceId }; + for (const target of [explicit, { workspace: "project" }]) { + await request(app).get(`${base}/list`).query(target).expect(404); + await request(app).get(`${base}/resolve`).query({ ...target, path: "report.txt" }).expect(404); + await request(app).get(`${base}/content`).query({ ...target, path: "report.txt" }).expect(404); + await request(app).get(`${base}/content`).query({ ...target, path: "report.txt", download: true }).expect(404); + } + const batch = await request(app).post(`${base}/availability`).send({ queries: [ + { path: "report.txt" }, { workspace: "execution", path: "report.txt" }, + { workspace: "project", path: "report.txt" }, { ...explicit, path: "report.txt" }, + ] }).expect(200); + expect(batch.body.results).toEqual([ + expect.objectContaining({ openable: true, resource: expect.objectContaining({ workspaceKind: "execution_workspace" }) }), + expect.objectContaining({ openable: true, resource: expect.objectContaining({ workspaceKind: "execution_workspace" }) }), + expect.objectContaining({ openable: false, unavailableReason: "not_found", resource: null }), + expect.objectContaining({ openable: false, unavailableReason: "not_found", resource: null }), + ]); + expect(JSON.stringify(batch.body)).not.toContain(projectRoot); + expect(JSON.stringify(batch.body)).not.toContain("PRIVATE_ALTERNATIVE_BYTES"); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it("reads projectless task files before and after organizational project assignment", async () => { + const { projectRoot, executionRoot } = await makeWorkspace(); + const graph = await seedGraph(db, { projectRoot, executionRoot }); + await db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, graph.issueId)); + await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, strategyType: "project_primary", providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await fs.writeFile(path.join(executionRoot, "report.txt"), "Task-owned report", "utf8"); + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + for (const projectId of [null, graph.targetProjectId]) { + await db.update(issues).set({ projectId }).where(eq(issues.id, graph.issueId)); + const result = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: "report.txt" }).expect(200); + expect(result.body.content.data).toBe("Task-owned report"); + expect(result.body.resource.workspaceKind).toBe("execution_workspace"); + } + }); + + it("checks shared physical-root privacy before every file read and retains moved or deleted source provenance", async () => { + const { root, projectRoot, executionRoot, targetProjectRoot } = await makeWorkspace(); + try { + const graph = await seedGraph(db, { projectRoot, executionRoot, targetProjectRoot }); + const canonicalCwd = await fs.realpath(executionRoot); + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const privateIssueId = crypto.randomUUID(); + await db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, graph.issueId)); + await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await db.update(projectWorkspaces).set({ cwd: executionRoot }).where(eq(projectWorkspaces.id, graph.projectWorkspaceId)); + await db.insert(issues).values({ id: privateIssueId, companyId: graph.companyId, title: "Private source task", visibility: "private", responsibleUserId: "private-owner" }); + const [source] = await db.insert(executionWorkspaces).values({ companyId: graph.companyId, sourceIssueId: privateIssueId, + name: "Private source workspace", cwd: executionRoot, mode: "shared_workspace", strategyType: "task_directory", providerType: "local_fs" }).returning(); + await fs.writeFile(path.join(executionRoot, "secret.txt"), "PRIVATE_ROOT_BYTES"); + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + const targets = [{ workspace: "execution" }, { projectId: graph.projectId, workspaceId: graph.projectWorkspaceId }]; + const reads = () => targets.flatMap(target => [ + request(app).get(`/api/issues/${graph.issueId}/file-resources/list`).query(target), + request(app).get(`/api/issues/${graph.issueId}/file-resources/resolve`).query({ ...target, path: "secret.txt" }), + request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ ...target, path: "secret.txt" }), + request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ ...target, path: "secret.txt", download: true }), + request(app).post(`/api/issues/${graph.issueId}/file-resources/availability`).send({ queries: [{ ...target, path: "secret.txt" }] }), + ]); + const deny = async () => { + for (const pending of reads()) { + const response = await pending; + if (response.body.kind === "workspace_file_availability") { + expect(response.status).toBe(200); + expect(response.body.results).toEqual([expect.objectContaining({ openable: false, unavailableReason: "not_found", resource: null })]); + } else expect(response.status).toBe(404); + for (const hidden of ["PRIVATE_ROOT_BYTES", source.id, privateIssueId, executionRoot, "Private source workspace"]) expect(JSON.stringify(response.body)).not.toContain(hidden); + } + }; + await deny(); + await db.update(executionWorkspaces).set({ status: "archived" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await deny(); + await db.update(executionWorkspaces).set({ status: "active" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, graph.companyId))).toEqual([]); + expect(await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, graph.companyId))).toEqual([]); + const [grant] = await db.insert(issueAccessGrants).values({ issueId: privateIssueId, subjectType: "user", subjectId: "board-user", source: "explicit" }).returning(); + for (const pending of reads()) expect((await pending).status).toBe(200); + const content = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: "secret.txt" }).expect(200); + expect(content.body.content.data).toBe("PRIVATE_ROOT_BYTES"); + await db.insert(executionWorkspaceRepositoryAccess).values({ companyId: graph.companyId, rootKey, canonicalCwd, kind: "source", scopeId: source.id }); + const provenance = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, graph.companyId)); + await db.update(executionWorkspaces).set({ cwd: targetProjectRoot }).where(eq(executionWorkspaces.id, source.id)); + await db.update(issueAccessGrants).set({ revokedAt: new Date() }).where(eq(issueAccessGrants.id, grant.id)); + await deny(); + await db.update(issueAccessGrants).set({ revokedAt: null }).where(eq(issueAccessGrants.id, grant.id)); + await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: "secret.txt" }).expect(200); + await db.delete(executionWorkspaces).where(eq(executionWorkspaces.id, source.id)); + await deny(); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, graph.companyId))).toEqual(provenance); + expect(await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, graph.companyId))).toEqual([]); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it("protects a private configured-project alias even without an execution workspace", async () => { + const { root, projectRoot, executionRoot, targetProjectRoot } = await makeWorkspace(); + try { + const graph = await seedGraph(db, { projectRoot, executionRoot, targetProjectRoot }); + await db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, graph.issueId)); + await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await db.update(projects).set({ visibility: "private" }).where(eq(projects.id, graph.targetProjectId)); + await db.update(projectWorkspaces).set({ cwd: executionRoot }).where(eq(projectWorkspaces.id, graph.targetProjectWorkspaceId)); + await fs.writeFile(path.join(executionRoot, "private-project.txt"), "PRIVATE_CONFIGURED_PROJECT"); + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: "private-project.txt" }).expect(404); + await db.insert(projectAccessMembers).values({ companyId: graph.companyId, projectId: graph.targetProjectId, subjectType: "user", subjectId: "board-user" }); + const response = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: "private-project.txt" }).expect(200); + expect(response.body.content.data).toBe("PRIVATE_CONFIGURED_PROJECT"); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it.each([ + ["configured", "ancestor"], ["configured", "descendant"], + ["execution", "ancestor"], ["execution", "descendant"], + ] as const)("protects %s private roots from a public %s file browser, including retained moved ownership", async (ownerKind, relation) => { + const { root, projectRoot, targetProjectRoot } = await makeWorkspace(); + try { + const ancestor = path.join(root, "work"), descendant = path.join(ancestor, "private"); + await fs.mkdir(descendant, { recursive: true }); + const publicRoot = relation === "ancestor" ? ancestor : descendant; + const privateRoot = relation === "ancestor" ? descendant : ancestor; + const relativeFile = relation === "ancestor" ? "private/secret.txt" : "secret.txt"; + await fs.writeFile(path.join(publicRoot, relativeFile), "PRIVATE_OVERLAPPING_ROOT_BYTES"); + const graph = await seedGraph(db, { projectRoot, targetProjectRoot, executionRoot: publicRoot }); + await db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, graph.issueId)); + await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)); + await db.update(projectWorkspaces).set({ cwd: publicRoot }).where(eq(projectWorkspaces.id, graph.projectWorkspaceId)); + let ownerId: string; + let privateIssueId: string | null = null; + if (ownerKind === "configured") { + ownerId = graph.targetProjectId; + await db.update(projects).set({ visibility: "private" }).where(eq(projects.id, ownerId)); + await db.update(projectWorkspaces).set({ cwd: privateRoot }).where(eq(projectWorkspaces.id, graph.targetProjectWorkspaceId)); + expect(await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.projectId, ownerId))).toEqual([]); + } else { + const [task] = await db.insert(issues).values({ companyId: graph.companyId, title: "Private overlapping task", visibility: "private", responsibleUserId: "private-owner" }).returning(); + privateIssueId = task.id; + const [workspace] = await db.insert(executionWorkspaces).values({ companyId: graph.companyId, sourceIssueId: task.id, + name: "Private overlapping workspace", cwd: privateRoot, mode: "shared_workspace", strategyType: "task_directory", providerType: "local_fs" }).returning(); + ownerId = workspace.id; + } + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + const targets = [{ workspace: "execution" }, { projectId: graph.projectId, workspaceId: graph.projectWorkspaceId }]; + const readState = async () => ({ + access: await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, graph.companyId)), + policies: await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, graph.companyId)), + }); + const deny = async () => { + const before = await readState(); + for (const target of targets) { + for (const [endpoint, query] of [ + ["list", {}], ["resolve", { path: relativeFile }], ["content", { path: relativeFile }], ["content", { path: relativeFile, download: true }], + ] as const) { + const response = await request(app).get(`/api/issues/${graph.issueId}/file-resources/${endpoint}`).query({ ...target, ...query }).expect(404); + for (const hidden of ["PRIVATE_OVERLAPPING_ROOT_BYTES", ownerId, privateRoot, "Private overlapping"]) expect(JSON.stringify(response.body)).not.toContain(hidden); + } + const availability = await request(app).post(`/api/issues/${graph.issueId}/file-resources/availability`) + .send({ queries: [{ ...target, path: relativeFile }] }).expect(200); + expect(availability.body.results).toEqual([expect.objectContaining({ openable: false, unavailableReason: "not_found", resource: null })]); + } + expect(await readState()).toEqual(before); + }; + await deny(); + const grant = ownerKind === "configured" + ? (await db.insert(projectAccessMembers).values({ companyId: graph.companyId, projectId: ownerId, subjectType: "user", subjectId: "board-user" }).returning())[0]! + : (await db.insert(issueAccessGrants).values({ issueId: privateIssueId!, subjectType: "user", subjectId: "board-user", source: "explicit" }).returning())[0]!; + const beforeAllowedReads = await readState(); + for (const target of targets) { + const response = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ ...target, path: relativeFile }).expect(200); + expect(response.body.content.data).toBe("PRIVATE_OVERLAPPING_ROOT_BYTES"); + } + expect(await readState()).toEqual(beforeAllowedReads); + const canonicalCwd = await fs.realpath(privateRoot); + await db.insert(executionWorkspaceRepositoryAccess).values({ companyId: graph.companyId, + rootKey: createHash("sha256").update(canonicalCwd).digest("hex"), canonicalCwd, + kind: ownerKind === "configured" ? "project_source" : "source", scopeId: ownerId }); + if (ownerKind === "configured") { + await db.update(projectWorkspaces).set({ cwd: targetProjectRoot }).where(eq(projectWorkspaces.id, graph.targetProjectWorkspaceId)); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.id, grant.id)); + } else { + await db.update(executionWorkspaces).set({ cwd: targetProjectRoot }).where(eq(executionWorkspaces.id, ownerId)); + await db.update(issueAccessGrants).set({ revokedAt: new Date() }).where(eq(issueAccessGrants.id, grant.id)); + } + await deny(); + expect(await fs.readFile(path.join(publicRoot, relativeFile), "utf8")).toBe("PRIVATE_OVERLAPPING_ROOT_BYTES"); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it("keeps sibling string-prefix roots independent without registering file readers", async () => { + const { root, projectRoot, targetProjectRoot } = await makeWorkspace(); + try { + const privateRoot = path.join(root, "work"), publicRoot = path.join(root, "worker"); + await fs.mkdir(privateRoot); await fs.mkdir(publicRoot); + await fs.writeFile(path.join(publicRoot, "public.txt"), "PUBLIC_SIBLING_BYTES"); + const graph = await seedGraph(db, { projectRoot, targetProjectRoot, executionRoot: publicRoot }); + await db.update(projects).set({ visibility: "private" }).where(eq(projects.id, graph.targetProjectId)); + await db.update(projectWorkspaces).set({ cwd: privateRoot }).where(eq(projectWorkspaces.id, graph.targetProjectWorkspaceId)); + const [privateIssue] = await db.insert(issues).values({ companyId: graph.companyId, title: "Private sibling", visibility: "private", responsibleUserId: "private-owner" }).returning(); + await db.insert(executionWorkspaces).values({ companyId: graph.companyId, sourceIssueId: privateIssue.id, cwd: privateRoot, + name: "Private sibling", mode: "shared_workspace", strategyType: "task_directory", providerType: "local_fs" }); + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + const target = { workspace: "execution", path: "public.txt" }; + const content = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query(target).expect(200); + expect(content.body.content.data).toBe("PUBLIC_SIBLING_BYTES"); + await request(app).get(`/api/issues/${graph.issueId}/file-resources/list`).query({ workspace: "execution" }).expect(200); + await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ ...target, download: true }).expect(200); + const availability = await request(app).post(`/api/issues/${graph.issueId}/file-resources/availability`).send({ queries: [target] }).expect(200); + expect(availability.body.results).toEqual([expect.objectContaining({ openable: true })]); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, graph.companyId))).toEqual([]); + expect(await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, graph.companyId))).toEqual([]); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it("does not confuse identical repositories in unrelated available roots with private file provenance", async () => { + const { root, projectRoot, executionRoot, targetProjectRoot } = await makeWorkspace(); + try { + const graph = await seedGraph(db, { projectRoot, executionRoot, targetProjectRoot }); + await db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, graph.issueId)); + const [publicWorkspace] = await db.update(executionWorkspaces).set({ projectId: null, projectWorkspaceId: null, providerType: "local_fs" }).where(eq(executionWorkspaces.sourceIssueId, graph.issueId)).returning(); + const [privateIssue] = await db.insert(issues).values({ companyId: graph.companyId, title: "Unrelated private repository", visibility: "private", responsibleUserId: "private-owner" }).returning(); + const [privateWorkspace] = await db.insert(executionWorkspaces).values({ companyId: graph.companyId, sourceIssueId: privateIssue.id, + name: "Unrelated source", cwd: targetProjectRoot, mode: "shared_workspace", strategyType: "task_directory", providerType: "local_fs" }).returning(); + const identity = "https://github.com/fixture/same-repo.git", relativePath = taskRepositoryRelativePath(identity); + for (const workspace of [publicWorkspace, privateWorkspace]) { + await fs.mkdir(path.join(workspace.cwd!, relativePath), { recursive: true }); + await fs.writeFile(path.join(workspace.cwd!, relativePath, "report.txt"), workspace.id === publicWorkspace.id ? "PUBLIC_COPY" : "PRIVATE_COPY"); + await db.insert(executionWorkspaceRepositories).values({ companyId: graph.companyId, executionWorkspaceId: workspace.id, + repositoryIdentity: identity, repoUrl: identity, relativePath, requestKey: workspace.id, state: "ready" }); + } + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + const response = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "execution", path: `${relativePath}/report.txt` }).expect(200); + expect(response.body.content.data).toBe("PUBLIC_COPY"); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + + it("reads a selected source independently of task project while retaining source privacy", async () => { + const { projectRoot, targetProjectRoot, executionRoot } = await makeWorkspace(); + const graph = await seedGraph(db, { projectRoot, targetProjectRoot, executionRoot }); + await fs.writeFile(path.join(projectRoot, "source.txt"), "Selected source", "utf8"); + const app = createApp(db, { type: "board", userId: "board-user", companyIds: [graph.companyId], source: "session", isInstanceAdmin: false }); + for (const projectId of [null, graph.targetProjectId]) { + await db.update(issues).set({ projectId }).where(eq(issues.id, graph.issueId)); + const result = await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "project", path: "source.txt" }).expect(200); + expect(result.body.content.data).toBe("Selected source"); + expect(result.body.resource.workspaceId).toBe(graph.projectWorkspaceId); + } + await db.update(projects).set({ visibility: "private" }).where(eq(projects.id, graph.projectId)); + await request(app).get(`/api/issues/${graph.issueId}/file-resources/content`).query({ workspace: "project", path: "source.txt" }).expect(404); + }); + it("auto-discovers unhinted same-company project files when issue workspaces miss", async () => { const { projectRoot, targetProjectRoot, executionRoot } = await makeWorkspace(); const graph = await seedGraph(db, { projectRoot, targetProjectRoot, executionRoot }); diff --git a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts index 8156d8c7e65..9441314f301 100644 --- a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts +++ b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts @@ -13,7 +13,9 @@ import { agentTaskSessions, agentWakeupRequests, agents, + authUsers, companies, + companyMemberships, companySkills, createDb, documentRevisions, @@ -107,6 +109,17 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { let tempDb: Awaited> | null = null; const tempRoots: string[] = []; + async function grantResponsibleUser(companyId: string) { + await db.insert(authUsers).values({ + id: "responsible-user", name: "Responsible user", email: "accepted-plan@example.test", + createdAt: new Date(), updatedAt: new Date(), + }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ + companyId, principalType: "user", principalId: "responsible-user", + status: "active", membershipRole: "member", + }); + } + beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-accepted-plan-workspace-"); db = createDb(tempDb.connectionString); @@ -154,6 +167,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { await db.delete(agents); await db.delete(workspaceOperations); await db.delete(companySkills); + await db.delete(companyMemberships); await db.delete(companies); }); @@ -302,6 +316,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId); await db.insert(projects).values({ id: projectId, companyId, @@ -476,6 +491,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId); await db.insert(projects).values({ id: projectId, companyId, @@ -712,6 +728,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId); await db.insert(projects).values({ id: projectId, companyId, @@ -871,6 +888,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId); await db.insert(projects).values({ id: projectId, companyId, @@ -1031,6 +1049,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId); await db.insert(projects).values({ id: projectId, companyId, diff --git a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts index b2f7805b391..eb9c505e5d7 100644 --- a/server/src/__tests__/heartbeat-comment-wake-batching.test.ts +++ b/server/src/__tests__/heartbeat-comment-wake-batching.test.ts @@ -6,6 +6,7 @@ import { FAST_RESPONSE_AGENT_GUIDANCE } from "@paperclipai/shared"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { agents, + authUsers, agentWakeupRequests, chatActions, chatConversations, @@ -218,6 +219,15 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { runningProcesses.clear(); }); + async function grantFixtureWorkspaceAccess(companyId: string, userIds: string[]) { + await db.insert(authUsers).values(userIds.map(id => ({ + id, name: "Fixture commenter", email: `${id}@example.test`, createdAt: new Date(), updatedAt: new Date(), + }))).onConflictDoNothing(); + await db.insert(companyMemberships).values(userIds.map(principalId => ({ + companyId, principalId, principalType: "user", status: "active", membershipRole: "member", + }))); + } + async function readGatewayWakePayload( payload: Record, ): Promise> { @@ -505,6 +515,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "user-1"]); await db.insert(agents).values({ id: agentId, @@ -1043,6 +1054,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "user-1"]); await db.insert(agents).values({ id: agentId, @@ -1253,6 +1265,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user"]); await db.insert(agents).values([ { @@ -1691,6 +1704,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "local-cli-user", "external-user"]); await db.insert(agents).values({ id: agentId, @@ -2391,6 +2405,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "local-cli-user", "user-1"]); await db.insert(agents).values({ id: agentId, @@ -2984,6 +2999,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "local-cli-user", "user-1"]); await db.insert(agents).values({ id: agentId, @@ -3149,6 +3165,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user"]); await db.insert(agents).values({ id: agentId, @@ -3314,6 +3331,7 @@ describeEmbeddedPostgres("heartbeat comment wake batching", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await grantFixtureWorkspaceAccess(companyId, ["responsible-user", "user-1"]); await db.insert(agents).values([ { diff --git a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts index cf4d7075d1d..48c922881e8 100644 --- a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts +++ b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts @@ -9,6 +9,8 @@ import { agentWakeupRequests, companySkills, companies, + authUsers, + companyMemberships, createDb, documentRevisions, documents, @@ -97,6 +99,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-heartbeat-dependency-scheduling-"); db = createDb(tempDb.connectionString); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture owner", email: "owner@example.test", createdAt: new Date(), updatedAt: new Date() }); heartbeat = heartbeatService(db); await ensureIssueRelationsTable(db); }, 20_000); @@ -158,6 +161,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = try { await db.transaction(async (tx) => { await tx.delete(companySkills); + await tx.delete(companyMemberships); await tx.delete(companies); }); break; @@ -199,6 +203,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = await db.insert(companies).values({ id: companyId, name: "Native handoff", issuePrefix: `N${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, name: "Parent", role: "engineer", status: "active", adapterType: "codex_local", adapterConfig: {}, permissions: {}, runtimeConfig: { heartbeat: { wakeOnDemand: true, maxConcurrentRuns: 1 } } }); @@ -265,6 +270,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = issuePrefix: `N${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -354,6 +360,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = it.each(["onboarding", "ordinary", "cancelled"])("dispatches only a verified completed onboarding report: %s", async kind => { const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), childId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Completion", issuePrefix: `R${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}` }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, name: "Lead", role: "engineer", status: "active", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: { heartbeat: { wakeOnDemand: true, maxConcurrentRuns: 1 } }, permissions: {} }); await db.insert(issues).values({ id: issueId, companyId, title: "Parent", status: kind === "cancelled" ? "cancelled" : "done", @@ -390,6 +397,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = issuePrefix: `R${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -486,6 +494,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -741,6 +750,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = issuePrefix: `D${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, requireBoardApprovalForNewAgents: false, }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -874,6 +884,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -1011,6 +1022,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -1211,6 +1223,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -1343,6 +1356,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, diff --git a/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts b/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts index ad30fa4034f..25916217a7d 100644 --- a/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts +++ b/server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { randomUUID } from "node:crypto"; import { and, eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; @@ -8,6 +9,7 @@ import { agentRuntimeState, budgetPolicies, companies, + authUsers, companyMemberships, companySkills, costEvents, @@ -93,7 +95,7 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-heartbeat-issue-liveness-"); db = createDb(tempDb.connectionString); - }, 30_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { // Dependency reconciliation heals missing wakes by enqueuing an @@ -128,6 +130,7 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () await db.delete(companyMemberships); await db.delete(companySkills); await db.delete(companies); + await db.delete(authUsers).where(eq(authUsers.id, "responsible-user")); }); afterAll(async () => { @@ -355,6 +358,8 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () defaultResponsibleUserId: "responsible-user", requireBoardApprovalForNewAgents: false, }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -431,11 +436,23 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () }), }); + const comments = await db.select().from(issueComments).where(eq(issueComments.issueId, issueId)); + const summaries = comments.filter((comment) => comment.authorType === "agent"); + expect(summaries).toHaveLength(2); + expect(summaries).toEqual(expect.arrayContaining( + [followUpRun!.id, recoveryWakes[0].runId].map((runId) => expect.objectContaining({ + authorAgentId: agentId, + onBehalfOfUserId: "responsible-user", + createdByRunId: runId, + body: "Acknowledged liveness escalation.", + })), + )); + const notices = comments.filter((comment) => comment.authorType !== "agent"); const issue = await db.select().from(issues).where(eq(issues.id, issueId)).then((rows) => rows[0]); if (disposition === "monitor") { expect(issue).toMatchObject({ status: "in_review", assigneeAgentId: agentId, monitorNextCheckAt: expect.any(Date) }); expect(await db.select().from(issueRecoveryActions)).toHaveLength(0); - expect(await db.select().from(issueComments)).toHaveLength(0); + expect(notices).toHaveLength(0); const attention = await issueService(db).listReviewAttention(companyId, [issue]); expect(attention.get(issueId)).toMatchObject({ state: "covered", paths: expect.arrayContaining([expect.objectContaining({ kind: "monitor" })]) }); return; @@ -447,7 +464,6 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () ownerType: "board", ownerAgentId: null, ownerUserId: null, returnOwnerAgentId: agentId, cause: "issue_review_path_lost", attemptCount: 1, maxAttempts: 1, }); - const notices = await db.select().from(issueComments).where(eq(issueComments.issueId, issueId)); expect(notices).toHaveLength(1); expect(notices[0]).toMatchObject({ authorType: "system", presentation: expect.objectContaining({ title: "No follow-up scheduled" }) }); @@ -455,7 +471,8 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", () // Replayed finalization must not duplicate the notice, action, or wake. expect(await escalateExhaustedIssueReviewPathRecovery(db, { run: recoveryRun, issueId })).toBeNull(); expect(await db.select().from(issueRecoveryActions)).toHaveLength(1); - expect(await db.select().from(issueComments)).toHaveLength(1); + const replayComments = await db.select().from(issueComments).where(eq(issueComments.issueId, issueId)); + expect(replayComments.map((comment) => comment.id).sort()).toEqual(comments.map((comment) => comment.id).sort()); expect(await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.reason, "issue_review_path_lost"))).toHaveLength(1); const feed = await attentionService(db).list(companyId, { userId: "responsible-user" }); diff --git a/server/src/__tests__/heartbeat-managed-clone-credentials.test.ts b/server/src/__tests__/heartbeat-managed-clone-credentials.test.ts index 74c1b602e79..ea2b997ad26 100644 --- a/server/src/__tests__/heartbeat-managed-clone-credentials.test.ts +++ b/server/src/__tests__/heartbeat-managed-clone-credentials.test.ts @@ -3,6 +3,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; +import { pathToFileURL } from "node:url"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { ensureManagedProjectWorkspace, prepareProjectRepositoryWorkspaces } from "../services/heartbeat.ts"; import { buildGitAuthInvocation, GIT_CREDENTIAL_TOKEN_ENV_KEY } from "../services/git-credentials.ts"; @@ -142,6 +143,76 @@ exit 128 } }); + it("clones a remote sibling with a missing configured cwd and rechecks destination access on reuse", async () => { + const source = await createLocalSourceRepo(); + const task = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-remote-sibling-")); + try { + const remote = "https://example.test/remote.git"; + const resolveGitAuth = vi.fn(async () => ({ + configArgs: ["-c", `url.${pathToFileURL(source).href}.insteadOf=${remote}`], + env: {}, source: "company_secret" as const, secretName: "fixture", + })); + const prepareLocalSource = vi.fn(async (cwd: string) => { + await fs.realpath(cwd); + return async () => {}; + }); + let destinationReadable = true; + const beforePublish = vi.fn(async () => { + if (!destinationReadable) throw new Error("Retained private destination is not authorized"); + }); + const input = { + cwd: task, anchorRepoUrl: null, resolveGitAuth, prepareLocalSource, beforePublish, + workspaces: [{ id: "remote", cwd: path.join(task, "missing"), repoUrl: remote, repoRef: null }], + }; + const [repo] = await prepareProjectRepositoryWorkspaces(input); + expect(await fs.readFile(path.join(repo!.cwd, "README.md"), "utf8")).toBe("hello\n"); + expect(prepareLocalSource).not.toHaveBeenCalled(); + expect(resolveGitAuth).toHaveBeenCalledTimes(1); + await fs.writeFile(path.join(repo!.cwd, "README.md"), "retained task edits"); + beforePublish.mockClear(); + expect(await prepareProjectRepositoryWorkspaces(input)).toEqual([repo]); + expect(beforePublish).toHaveBeenCalledTimes(1); + expect(resolveGitAuth).toHaveBeenCalledTimes(1); + expect(await fs.readFile(path.join(repo!.cwd, "README.md"), "utf8")).toBe("retained task edits"); + destinationReadable = false; + await expect(prepareProjectRepositoryWorkspaces(input)).rejects.toThrow("Retained private destination is not authorized"); + expect(prepareLocalSource).not.toHaveBeenCalled(); + expect(await fs.readFile(path.join(repo!.cwd, "README.md"), "utf8")).toBe("retained task edits"); + } finally { + await Promise.all([source, task].map((cwd) => fs.rm(cwd, { recursive: true, force: true }))); + } + }); + + it.each(["file_url", "absolute_path"] as const)("authorizes the actual %s fallback when the configured cwd is missing", async (transport) => { + const source = await createLocalSourceRepo(); + const task = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-local-sibling-")); + try { + let sourceReadable = false; + const recheck = vi.fn(async () => {}); + const prepareLocalSource = vi.fn(async (cwd: string) => { + expect(cwd).toBe(source); + if (!sourceReadable) throw new Error("Private source is not authorized"); + return recheck; + }); + const input = { + cwd: task, anchorRepoUrl: null, prepareLocalSource, + workspaces: [{ id: "local", cwd: path.join(task, "missing"), + repoUrl: transport === "file_url" ? pathToFileURL(source).href : source, repoRef: null }], + }; + await expect(prepareProjectRepositoryWorkspaces(input)).rejects.toThrow("Private source is not authorized"); + expect(await fs.readdir(path.join(task, ".paperclip-repositories"))).toEqual([]); + sourceReadable = true; + await fs.writeFile(path.join(source, "README.md"), "uncommitted source edits"); + const [repo] = await prepareProjectRepositoryWorkspaces(input); + expect(await fs.readFile(path.join(repo!.cwd, "README.md"), "utf8")).toBe("hello\n"); + expect(recheck).toHaveBeenCalled(); + sourceReadable = false; + await expect(prepareProjectRepositoryWorkspaces(input)).rejects.toThrow("Private source is not authorized"); + } finally { + await Promise.all([source, task].map((cwd) => fs.rm(cwd, { recursive: true, force: true }))); + } + }); + it("fails task preparation when any attached repository cannot be cloned", async () => { const first = await createLocalSourceRepo(); try { diff --git a/server/src/__tests__/heartbeat-native-cleanup-admission.test.ts b/server/src/__tests__/heartbeat-native-cleanup-admission.test.ts index 5660976284c..b6544447a68 100644 --- a/server/src/__tests__/heartbeat-native-cleanup-admission.test.ts +++ b/server/src/__tests__/heartbeat-native-cleanup-admission.test.ts @@ -1,7 +1,8 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { agents, companies, createDb, heartbeatRuns, issues } from "@paperclipai/db"; +import { agents, authUsers, companies, companyMemberships, createDb, heartbeatRuns, issues } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; import { heartbeatService } from "../services/heartbeat.js"; @@ -19,7 +20,7 @@ suite("native terminal-run cleanup admission", () => { beforeAll(async () => { temporary = await startEmbeddedPostgresTestDatabase("native-cleanup-admission-"); db = createDb(temporary.connectionString); - }, 60_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterAll(async () => { await db?.$client.end({ timeout: 0 }); await temporary?.cleanup(); @@ -28,6 +29,8 @@ suite("native terminal-run cleanup admission", () => { it("holds the same task until its native executor settles while allowing unrelated tasks", async () => { const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), otherIssueId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Cleanup admission", issuePrefix: "CLN", defaultResponsibleUserId: "owner" }); + await db.insert(authUsers).values({ id: "owner", name: "Fixture user", email: "owner@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "owner", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, name: "Worker", role: "engineer", status: "idle", adapterType: "process", adapterConfig: {}, permissions: {}, runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: true, maxConcurrentRuns: 20 } } }); await db.insert(issues).values([issueId, otherIssueId].map(id => ({ id, companyId, title: "Recovery task", status: "todo", assigneeAgentId: agentId, responsibleUserId: "owner" }))); diff --git a/server/src/__tests__/heartbeat-plugin-environment.test.ts b/server/src/__tests__/heartbeat-plugin-environment.test.ts index 667bd68ac21..3965b0ef721 100644 --- a/server/src/__tests__/heartbeat-plugin-environment.test.ts +++ b/server/src/__tests__/heartbeat-plugin-environment.test.ts @@ -5,7 +5,9 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { agents, + authUsers, companies, + companyMemberships, createDb, environments, executionWorkspaces, @@ -61,6 +63,16 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { const tempRoots: string[] = []; const heartbeats: ReturnType[] = []; + async function grantResponsibleUser(companyId: string, userId: string) { + await db.insert(authUsers).values({ + id: userId, name: "Responsible user", email: `${userId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ + companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member", + }); + } + beforeAll(async () => { const started = await startEmbeddedPostgresTestDatabase("heartbeat-plugin-environment"); stopDb = started.cleanup; @@ -118,6 +130,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId, "responsible-user"); await db.insert(projects).values({ id: projectId, companyId, @@ -363,6 +376,8 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { updatedAt: new Date(), }, ]); + await grantResponsibleUser(companyAId, "responsible-user-a"); + await grantResponsibleUser(companyBId, "responsible-user-b"); await db.insert(projects).values([ { id: projectAId, @@ -527,6 +542,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { createdAt: new Date(), updatedAt: new Date(), }); + await grantResponsibleUser(companyId, "responsible-user"); await db.insert(projects).values({ id: projectId, companyId, diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index 3b2005cc961..f9ce2234e52 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import * as conversationContinuation from "../services/conversation-continuation.js"; import { environmentRuntimeService } from "../services/environment-runtime.js"; import * as workspaceRuntime from "../services/workspace-runtime.js"; @@ -515,7 +516,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { createdAt: now, updatedAt: now, }); - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { vi.clearAllMocks(); @@ -682,6 +683,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { await db.delete(companySecretBindings); await db.delete(companySecrets); try { + await db.delete(companyMemberships); await db.delete(companies); break; } catch (error) { @@ -1176,6 +1178,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { requireBoardApprovalForNewAgents: false, }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -1462,6 +1465,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { requireBoardApprovalForNewAgents: false, }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, @@ -1611,7 +1615,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { it.each(["missing_default", "credential_not_shared", "database_error", "unmarked_http_error", "unmarked_forbidden", "provider_error"] as const)( "preserves AI configuration recovery and reports only unexpected causes: %s", async (cause) => { const { companyId, agentId, issueId, runId, wakeupRequestId } = await seedQueuedIssueRunFixture(); - await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }).onConflictDoNothing(); await db.update(agents).set({ runtimeConfig: { heartbeat: { wakeOnDemand: true, maxConcurrentRuns: 1 }, aiConnection: { provider: "openai", method: "api_key", mode: "responsible_user" }, @@ -3334,11 +3338,18 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { }); }); - it("dispatches local native external chat inside the server-selected task root", async () => { + it.each(["projectless", "organization_only", "empty_source"] as const)("dispatches local native external chat inside the server-selected task root: %s", async placement => { await withTempPaperclipHome(async () => { const { companyId, agentId, issueId, runId } = await seedQueuedIssueRunFixture(); await fs.mkdir(resolvePaperclipInstanceRoot(), { recursive: true }); + const projectId = placement === "projectless" ? null : randomUUID(); + if (projectId) { + await db.insert(projects).values({ id: projectId, companyId, name: "Chat channel project default" }); + if (placement === "empty_source") { + await db.insert(projectWorkspaces).values({ companyId, projectId, name: "Unconfigured source", cwd: " ", repoUrl: null }); + } + } await db .update(agents) .set({ @@ -3348,7 +3359,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { .where(eq(agents.id, agentId)); await db .update(issues) - .set({ originKind: "chat_channel" }) + .set({ originKind: "chat_channel", projectId }) .where(eq(issues.id, issueId)); await db .update(heartbeatRuns) @@ -3378,15 +3389,59 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { expect(input.workspace.cwd).toBe( path.join( await fs.realpath(resolvePaperclipInstanceRoot()), - "chat-workspaces", + "isolated-workspaces", companyId, - agentId, issueId, ), ); expect(input.workspace.cwd).not.toBe( resolveDefaultAgentWorkspaceDir(agentId), ); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, task.executionWorkspaceId!)); + expect(task.projectId).toBe(projectId); + expect(workspace).toMatchObject({ sourceIssueId: issueId, projectId: null, projectWorkspaceId: null, cwd: input.workspace.cwd }); + expect(mockAdapterExecute).not.toHaveBeenCalled(); + }); + }); + + it.each(["managed_isolated", "shared"] as const)("prepares a fresh native chat configured source safely: %s", async (mode) => { + await withTempPaperclipHome(async () => { + const { companyId, agentId, issueId, runId } = await seedQueuedIssueRunFixture(); + const instanceRoot = resolvePaperclipInstanceRoot(); + await fs.mkdir(instanceRoot, { recursive: true }); + const cwd = path.join(instanceRoot, "source-repository"); + await fs.mkdir(cwd, { recursive: true }); + execFileSync("git", ["init", "-b", "main"], { cwd, stdio: "ignore" }); + await fs.writeFile(path.join(cwd, "report.txt"), "source bytes"); + execFileSync("git", ["add", "report.txt"], { cwd, stdio: "ignore" }); + execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.test", "commit", "-m", "Source"], { cwd, stdio: "ignore" }); + const [project] = await db.insert(projects).values({ companyId, name: "Chat source" }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId, projectId: project.id, name: "Source", cwd, isPrimary: true }).returning(); + await db.update(agents).set({ adapterType: "paperclip_runner", adapterConfig: { provider: "codex", model: "gpt-5.6-luna" } }).where(eq(agents.id, agentId)); + await db.update(issues).set({ originKind: "chat_channel", projectWorkspaceId: source.id, + workspaceSelection: { version: 1, source: "channel", selection: { kind: "configured_source", projectWorkspaceId: source.id, mode } }, + executionWorkspaceSettings: { mode: mode === "managed_isolated" ? "isolated_workspace" : "shared_workspace" }, + }).where(eq(issues.id, issueId)); + await db.update(heartbeatRuns).set({ runtimeMode: "native", runtimeModeResolvedAt: new Date(), nativeIssueId: issueId }).where(eq(heartbeatRuns.id, runId)); + const nativeSessionBackendFactory = vi.fn((_execution: { workspace: { cwd: string } }) => { throw new NativeRunnerOwnershipUnverifiedError(); }); + const heartbeat = heartbeatService(db, { nativeSessionBackendFactory }); + await heartbeat.resumeQueuedRuns(); + await waitForValue(async () => nativeSessionBackendFactory.mock.calls.length > 0 || Boolean((await heartbeat.getRun(runId))?.errorCode), 15_000); + await heartbeat.waitForRunExecutionDrain(runId); + if (mode === "shared") { + expect(nativeSessionBackendFactory).not.toHaveBeenCalled(); + expect((await heartbeat.getRun(runId))?.error).toContain("External chat requires a task-owned isolated workspace"); + } else { + expect(nativeSessionBackendFactory).toHaveBeenCalledTimes(1); + const input = nativeSessionBackendFactory.mock.calls[0]![0]; + expect(await fs.realpath(input.workspace.cwd)).not.toBe(await fs.realpath(cwd)); + expect(await fs.readFile(path.join(input.workspace.cwd, "report.txt"), "utf8")).toBe("source bytes"); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, task.executionWorkspaceId!)); + expect(workspace).toMatchObject({ sourceIssueId: issueId, projectId: project.id, mode: "isolated_workspace", strategyType: "git_worktree", cwd: input.workspace.cwd, providerRef: input.workspace.cwd }); + } + expect(await fs.readFile(path.join(cwd, "report.txt"), "utf8")).toBe("source bytes"); expect(mockAdapterExecute).not.toHaveBeenCalled(); }); }); @@ -5803,7 +5858,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { mockAdapterExecute.mockClear(); }); - it("blocks a git-sensitive local adapter before launch when a project-workspace-linked issue is missing its project id", async () => { + it("blocks a projectless task before launch when its configured source directory is unavailable", async () => { mockAdapterExecute.mockClear(); const { companyId, agentId, runId, issueId } = await seedQueuedIssueRunFixture(); @@ -5853,7 +5908,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { errorCode: "workspace_validation_failed", }); expect(failedRun?.error).toContain( - "linked to a project workspace but has no project id", + "Configured project workspace is unavailable", ); // The adapter process never started, so no agent could post an issue // comment. The comment policy is not_applicable and no missing-comment @@ -5872,8 +5927,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { expect(missingCommentWakeups).toHaveLength(0); expect(failedRun?.resultJson).toMatchObject({ workspaceValidation: { - reason: "missing_project_id", - adapterType: "codex_local", + reason: "configured_workspace_unavailable", issueId, issueProjectId: null, issueProjectWorkspaceId: projectWorkspaceId, @@ -6048,8 +6102,10 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { executionRecovery: { kind: "bootstrap", providerWorkStarted: false }, }, }); - expect(run.error).toContain("checkout could not be prepared"); + expect(run.error).toContain("Configured project workspace is unavailable"); + expect(run.error).toContain("Repair the configured path or repository access"); const validation = run.resultJson!.workspaceValidation as Record; + expect(validation.baseCwdFallback).toBe(false); const materializations = validation.materializationFailures as Array>; expect(materializations).toHaveLength(failures.length); expect(materializations.map((failure) => failure.connectionFailure)).toEqual(failures.map((failure) => @@ -6358,7 +6414,8 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { legacyDispositionEpisode: { id: runId, attempt: 1, maxAttempts: 2 }, dispositionRepairInstruction: LEGACY_DISPOSITION_REPAIR_INSTRUCTION, }); - expect(repair?.status).toBe("succeeded"); + expect({ status: repair?.status, errorCode: repair?.errorCode, error: repair?.error }) + .toEqual({ status: "succeeded", errorCode: null, error: null }); expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].status).toBe("done"); expect(mockAdapterExecute).toHaveBeenCalledTimes(2); expect(await db.select().from(issueComments).where(eq(issueComments.issueId, issueId))).not.toEqual(expect.arrayContaining([expect.objectContaining({body: SUCCESSFUL_RUN_HANDOFF_REQUIRED_NOTICE_BODY})])); diff --git a/server/src/__tests__/heartbeat-project-repositories.test.ts b/server/src/__tests__/heartbeat-project-repositories.test.ts index a619a67cef5..9214e504e1c 100644 --- a/server/src/__tests__/heartbeat-project-repositories.test.ts +++ b/server/src/__tests__/heartbeat-project-repositories.test.ts @@ -1,19 +1,29 @@ -import { randomUUID } from "node:crypto"; +import express from "express"; +import request from "supertest"; +import { fileResourceRoutes } from "../routes/file-resources.js"; +import { errorHandler } from "../middleware/index.js"; +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { createHash, randomUUID } from "node:crypto"; import { and, eq } from "drizzle-orm"; import { execFileSync } from "node:child_process"; -import { mkdtemp, mkdir, writeFile, readFile, realpath, rm } from "node:fs/promises"; +import { mkdtemp, mkdir, writeFile, readFile, realpath, readdir, rename, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; -import { agents, companies, createDb, environments, executionWorkspaces, heartbeatRuns, issues, projects, projectWorkspaces } from "@paperclipai/db"; +import { agents, authUsers, companies, companyMemberships, createDb, environments, executionWorkspaces, heartbeatRuns, issues, issueAccessGrants, principalPermissionGrants, projects, projectAccessMembers, projectWorkspaces } from "@paperclipai/db"; import { setExpensiveWorkspaceGitExecutor } from "@paperclipai/adapter-utils/git-workspace-sync"; import { buildProjectMentionHref } from "@paperclipai/shared"; import { createWorkspaceGitOperationScheduler, WorkspaceGitScanError } from "../services/workspace-git-operation-scheduler.js"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; import { heartbeatService } from "../services/heartbeat.ts"; +import { createHeartbeatWorkspaceResolver } from "../services/heartbeat/workspaces.js"; +import { resolveManagedProjectWorkspaceDir, resolvePaperclipInstanceRoot } from "../home-paths.js"; +import { accessService } from "../services/access.js"; +import { assertExecutionWorkspaceRootWriteAccess } from "../services/execution-workspace-repositories.js"; import { instanceSettingsService } from "../services/instance-settings.ts"; import { environmentRuntimeService } from "../services/environment-runtime.js"; +import { findNativeChatWorkspaceScope } from "../services/native-runtime/native-chat-workspace.js"; import { drainHeartbeatRunsToQuiescence } from "./helpers/drain-heartbeat-runs.js"; const execute = vi.hoisted(() => vi.fn(async (_input: any) => ({ exitCode: 0, signal: null, timedOut: false }))); @@ -36,12 +46,13 @@ suite("task project repository provisioning", () => { vi.stubEnv("PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC", "false"); database = await startEmbeddedPostgresTestDatabase("project-repositories"); db = createDb(database.connectionString); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture owner", email: "owner@example.test", createdAt: new Date(), updatedAt: new Date() }); heartbeat = heartbeatService(db); execute.mockImplementation(async (input) => { await db.update(issues).set({ status: "done" }).where(eq(issues.id, input.context.issueId)); return { exitCode: 0, signal: null, timedOut: false }; }); - }, 30_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterAll(async () => { if (db && heartbeat) await drainHeartbeatRunsToQuiescence(db, heartbeat); await db?.$client.end({ timeout: 5 }); @@ -59,13 +70,18 @@ suite("task project repository provisioning", () => { }); }); - it("isolates repository-free low-trust tasks while preserving reassignment and authorized referenced projects", async () => { + it.each(["agent", "organization"] as const)("isolates repository-free low-trust tasks while preserving reassignment and authorized referenced projects (%s policy)", async policyOwner => { const companyId = randomUUID(), projectId = randomUUID(), agentId = randomUUID(), environmentId = randomUUID(); const referencedProjectId = randomUUID(), deniedProjectId = randomUUID(); vi.stubEnv("PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC", "true"); await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); await db.insert(companies).values({ id: companyId, name: "Email company", issuePrefix: `E${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); - await db.insert(projects).values({ id: projectId, companyId, name: "Onboarding" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + const authorizationPolicy = { + trustPreset: "low_trust_review", trustBoundary: { mode: "low_trust_review", companyId, projectIds: [projectId, referencedProjectId] }, + }; + await db.insert(projects).values({ id: projectId, companyId, name: "Onboarding", + executionWorkspacePolicy: policyOwner === "organization" ? { authorizationPolicy } : null }); for (const id of [referencedProjectId, deniedProjectId]) { const source = path.join(root, companyId, id); await mkdir(source, { recursive: true }); @@ -73,13 +89,11 @@ suite("task project repository provisioning", () => { await db.insert(projects).values({ id, companyId, name: id }); await db.insert(projectWorkspaces).values({ id: randomUUID(), companyId, projectId: id, name: "Reference", sourceType: "local_path", cwd: source, isPrimary: true }); } - await db.insert(environments).values({ id: environmentId, name: "Email sandbox", driver: "sandbox", status: "active", config: { provider: "fake", image: "fake:test" } }); + await db.insert(environments).values({ id: environmentId, name: `Email sandbox ${policyOwner}`, driver: "sandbox", status: "active", config: { provider: "fake", image: "fake:test" } }); await db.insert(agents).values({ id: agentId, companyId, name: "Email agent", role: "engineer", status: "idle", adapterType: "codex_local", defaultEnvironmentId: environmentId, adapterConfig: {}, runtimeConfig: {}, - permissions: { trustPreset: "low_trust_review", authorizationPolicy: { - trustPreset: "low_trust_review", trustBoundary: { mode: "low_trust_review", companyId, projectIds: [projectId, referencedProjectId] }, - } }, + permissions: policyOwner === "agent" ? { trustPreset: "low_trust_review", authorizationPolicy } : {}, }); // Exercise the real lease and workspace lifecycle with the built-in fake // provider, executing its setup commands in a disposable filesystem root. @@ -109,11 +123,16 @@ suite("task project repository provisioning", () => { }, { timeout: 15_000 }); await drainHeartbeatRunsToQuiescence(db, sandboxHeartbeat); const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.sourceIssueId, issueId)); - expect(workspace).toMatchObject({ companyId, projectId, mode: "isolated_workspace", strategyType: "project_primary" }); + expect(workspace).toMatchObject({ companyId, projectId: null, mode: "isolated_workspace", strategyType: "project_primary" }); expect(workspace.cwd).toContain(`/isolated-workspaces/${companyId}/${issueId}`); expect(execute.mock.calls.filter(([input]) => input.runId === run!.id)).toHaveLength(1); const call = execute.mock.calls.find(([input]) => input.runId === run!.id)![0]; expect(call.context.paperclipEnvironment.driver).toBe("sandbox"); + expect((await sandboxHeartbeat.getRun(run!.id))?.contextSnapshot).toMatchObject({ + executionPolicy: { trustPreset: "low_trust_review", authorizationPolicy: { + trustBoundary: { projectIds: [projectId, referencedProjectId].sort() }, + } }, + }); expect(call.context.paperclipWorkspaces.map((workspace: { projectId: string }) => workspace.projectId)).toEqual([referencedProjectId]); directories.push(workspace.cwd!); await writeFile(path.join(workspace.cwd!, "email.txt"), `private email ${index}`); @@ -147,6 +166,7 @@ suite("task project repository provisioning", () => { enableIsolatedWorkspacesByDefault: true, }); await db.insert(companies).values({ id: companyId, name: "Research", issuePrefix: `R${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); await db.insert(projects).values({ id: projectId, companyId, name: "Research", status: "in_progress", executionWorkspacePolicy: explicitIsolation === "project" ? { enabled: true, defaultMode: "isolated_workspace" } : null, @@ -186,11 +206,449 @@ suite("task project repository provisioning", () => { expect(workspace.strategy).toBe("git_worktree"); expect(execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: workspace.cwd, encoding: "utf8" }).trim()).toBe("true"); } else { - expect(workspace.cwd).toContain(`${projectId}/_default`); + expect(workspace.cwd).toContain(`/isolated-workspaces/${companyId}/${issueId}`); } } }, 25_000); + it.each([ + { name: "task override without agent cwd", agentCwd: false, taskDirectory: false }, + { name: "task override replacing agent cwd", agentCwd: true, taskDirectory: false }, + { name: "explicit task directory ahead of cwd overrides", agentCwd: true, taskDirectory: true }, + ])("resolves $name before binding and retains that root on continuation", async ({ agentCwd, taskDirectory }) => { + const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const overrideCwd = path.join(root, companyId, "task-override"); + const agentDefaultCwd = path.join(root, companyId, "agent-default"); + const laterCwd = path.join(root, companyId, "later-override"); + for (const cwd of [overrideCwd, agentDefaultCwd, laterCwd]) { + await mkdir(cwd, { recursive: true }); + await writeFile(path.join(cwd, "input.txt"), cwd); + } + await db.insert(companies).values({ id: companyId, name: "Task cwd", issuePrefix: `W${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(agents).values({ id: agentId, companyId, name: "Worker", status: "idle", adapterType: "codex_local", adapterConfig: agentCwd ? { cwd: agentDefaultCwd } : {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, title: "Use task files", status: "todo", assigneeAgentId: agentId, + assigneeAdapterOverrides: { adapterConfig: { cwd: overrideCwd } }, + workspaceSelection: taskDirectory ? { version: 1, source: "explicit", selection: { kind: "task_directory" } } : null }); + const expectedCwd = taskDirectory + ? path.join(resolvePaperclipInstanceRoot(), "isolated-workspaces", companyId, issueId) + : overrideCwd; + let bindingId: string | null = null; + for (let admission = 0; admission < 2; admission++) { + const defaultExecute = execute.getMockImplementation()!; + execute.mockImplementationOnce(async (input) => { + const cwd = input.context.paperclipWorkspace.cwd; + expect(await realpath(cwd)).toBe(await realpath(expectedCwd)); + if (!taskDirectory) expect(await readFile(path.join(cwd, "input.txt"), "utf8")).toBe(overrideCwd); + if (admission === 1) expect(await readFile(path.join(cwd, "output.txt"), "utf8")).toBe("first admission"); + await writeFile(path.join(cwd, "output.txt"), admission === 0 ? "first admission" : "continued"); + return defaultExecute(input); + }); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + await vi.waitFor(async () => { expect(await heartbeat.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }); }, { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + expect(execute.mock.calls.filter(([input]) => input.runId === run!.id)).toHaveLength(1); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, task.executionWorkspaceId!)); + expect(workspace.projectId).toBeNull(); + expect(await realpath(workspace.cwd!)).toBe(await realpath(expectedCwd)); + if (bindingId) expect(workspace.id).toBe(bindingId); + bindingId = workspace.id; + await db.update(issues).set({ status: "todo", assigneeAdapterOverrides: { adapterConfig: { cwd: laterCwd } } }).where(eq(issues.id, issueId)); + } + expect(await readFile(path.join(expectedCwd, "output.txt"), "utf8")).toBe("continued"); + await expect(readFile(path.join(laterCwd, "output.txt"), "utf8")).rejects.toThrow(); + await expect(readFile(path.join(agentDefaultCwd, "output.txt"), "utf8")).rejects.toThrow(); + }, 40_000); + + it.each([false, true])("keeps a task directory source projectless across chat admissions with an organizational project (explicit=%s)", async explicit => { + const companyId = randomUUID(), projectId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + await db.insert(companies).values({ id: companyId, name: "Chat files", issuePrefix: `C${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Organization only" }); + await db.insert(agents).values({ id: agentId, companyId, name: "Chat agent", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, projectId, title: "Keep chat files", originKind: "chat_channel", status: "todo", assigneeAgentId: agentId, + workspaceSelection: explicit ? { version: 1, source: "explicit", selection: { kind: "task_directory" } } : null }); + let bindingId: string | undefined; + for (let admission = 0; admission < 2; admission++) { + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId, projectId } }); + await vi.waitFor(async () => { expect(await heartbeat.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }); }, { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, task.executionWorkspaceId!)); + expect(workspace.projectId).toBeNull(); + expect(task.projectId).toBe(projectId); + if (bindingId) expect(workspace.id).toBe(bindingId); + bindingId = workspace.id; + const scope = await findNativeChatWorkspaceScope(db, { companyId, agentId, issueId, instanceRoot: root, + adapterType: "paperclip_runner", environmentDriver: "local" }); + expect(scope?.projectId).toBeNull(); + } + }, 40_000); + + it.each(["task_directory", "configured_source", "default"] as const)("keeps organizational environment separate from %s files", async (kind) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceProjectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "source"); + await mkdir(cwd, { recursive: true }); + await db.insert(companies).values({ id: companyId, name: "Environment ownership", issuePrefix: `V${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values([ + { id: projectId, companyId, name: "Task project", env: { PROJECT_VALUE: "task", TASK_ONLY: "present" } }, + { id: sourceProjectId, companyId, name: "File source", env: { PROJECT_VALUE: "source", SOURCE_ONLY: "must-not-leak" } }, + ]); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId: sourceProjectId, name: "Source", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Worker", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, projectId, title: "Use task environment", status: "todo", assigneeAgentId: agentId, executionWorkspaceSettings: { mode: "shared_workspace" }, + projectWorkspaceId: kind === "configured_source" ? sourceId : null, + workspaceSelection: kind === "default" ? null : { version: 1, source: "explicit", selection: kind === "configured_source" + ? { kind, projectWorkspaceId: sourceId, mode: "shared" } : { kind } }, + }); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + await vi.waitFor(async () => { expect(await heartbeat.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }); }, { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const call = execute.mock.calls.find(([input]) => input.runId === run!.id)![0]; + expect(call.config.env).toMatchObject({ PROJECT_VALUE: "task", TASK_ONLY: "present" }); + expect(call.config.env.SOURCE_ONLY).toBeUndefined(); + expect(call.context.projectId).toBe(projectId); + if (kind === "configured_source") expect(await realpath(call.context.paperclipWorkspace.cwd)).toBe(await realpath(cwd)); + }, 25_000); + + it.each([ + { policy: "organization", selection: "task_directory" }, + { policy: "organization", selection: "default" }, + { policy: "organization", selection: "configured_source" }, + { policy: "source", selection: "configured_source" }, + ] as const)("retains $policy low-trust restrictions with $selection", async ({ policy, selection }) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceProjectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "source"); + await mkdir(cwd, { recursive: true }); + await db.insert(companies).values({ id: companyId, name: "Policy ownership", issuePrefix: `L${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + const restricted = { authorizationPolicy: { trustPreset: "low_trust_review", trustBoundary: { mode: "low_trust_review", companyId, issueIds: [issueId] } } }; + await db.insert(projects).values([ + { id: projectId, companyId, name: "Task project", executionWorkspacePolicy: policy === "organization" ? restricted : null }, + { id: sourceProjectId, companyId, name: "File source", executionWorkspacePolicy: policy === "source" ? restricted : null }, + ]); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId: sourceProjectId, name: "Source", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Worker", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, projectId, title: "Do not relax policy", status: "todo", assigneeAgentId: agentId, executionWorkspaceSettings: { mode: "shared_workspace" }, + projectWorkspaceId: selection === "configured_source" ? sourceId : null, + workspaceSelection: selection === "default" ? null : { version: 1, source: "explicit", selection: selection === "configured_source" + ? { kind: selection, projectWorkspaceId: sourceId, mode: "shared" } : { kind: selection } }, + }); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + await vi.waitFor(async () => { expect(await heartbeat.getRun(run!.id)).toMatchObject({ status: "failed" }); }, { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + expect(execute.mock.calls.filter(([input]) => input.runId === run!.id)).toHaveLength(0); + expect((await heartbeat.getRun(run!.id))?.contextSnapshot).toMatchObject({ executionPolicy: { trustPreset: "low_trust_review", authorizationPolicy: { trustBoundary: { issueIds: [issueId] } } } }); + }, 25_000); + + it.each(["shared_workspace", "isolated_workspace"] as const)("blocks shared source adapter admission after grant revocation with retained mode %s", async (retainedMode) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "protected-shared-source"); + await mkdir(cwd, { recursive: true }); + await writeFile(path.join(cwd, "work.txt"), "Protected project files"); + await db.insert(companies).values({ id: companyId, name: "Shared source authority", issuePrefix: `W${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Readable protected source", executionWorkspacePolicy: { + authorizationPolicy: { assignmentPolicy: { mode: "protected" } }, + } }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Shared source", sourceType: "local_path", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Writer", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(companyMemberships).values({ companyId, principalType: "agent", principalId: agentId, status: "active", membershipRole: "member" }); + const [grant] = await db.insert(principalPermissionGrants).values({ companyId, principalType: "agent", principalId: agentId, + permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }).returning(); + await db.insert(principalPermissionGrants).values({ companyId, principalType: "user", principalId: "responsible-user", + permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }); + await db.insert(issues).values({ id: issueId, companyId, title: "Work in another project's shared files", status: "todo", assigneeAgentId: agentId, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "shared_workspace" }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "shared" } } }); + const admit = async () => { + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + return (await heartbeat.getRun(run!.id))!; + }; + const first = await admit(); + expect(first).toMatchObject({ status: "succeeded", error: null }); + expect(execute.mock.calls.filter(([input]) => input.runId === first.id)).toHaveLength(1); + const [bound] = await db.select().from(issues).where(eq(issues.id, issueId)); + expect(bound.projectId).toBeNull(); + expect(bound.executionWorkspaceId).not.toBeNull(); + if (retainedMode === "isolated_workspace") { + await db.update(executionWorkspaces).set({ mode: retainedMode }).where(eq(executionWorkspaces.id, bound.executionWorkspaceId!)); + await db.update(issues).set({ executionWorkspaceSettings: { mode: retainedMode, workspaceStrategy: { type: "project_primary" } } }).where(eq(issues.id, issueId)); + } + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + const actor = { type: "agent" as const, agentId, companyId, source: "agent_jwt" as const, onBehalfOfUserId: "responsible-user" }; + expect(await accessService(db).decide({ actor, action: "project:read", resource: { type: "project", companyId, projectId } })).toMatchObject({ allowed: true }); + expect(await accessService(db).decide({ actor, action: "tasks:assign", resource: { type: "issue", companyId, projectId, assigneeAgentId: agentId }, + scope: { projectId, assigneeAgentId: agentId } })).toMatchObject({ allowed: false, reason: "deny_policy_restricted" }); + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const denied = await admit(); + expect(denied.status).toBe("failed"); + expect(denied.error).toContain("Target project is protected and requires an explicit assignment grant"); + expect(execute.mock.calls.filter(([input]) => input.runId === denied.id)).toHaveLength(0); + const [retained] = await db.select().from(issues).where(eq(issues.id, issueId)); + expect(retained.executionWorkspaceId).toBe(bound.executionWorkspaceId); + expect(await readFile(path.join(cwd, "work.txt"), "utf8")).toBe("Protected project files"); + }, 40_000); + + it.each(["task", "agent"] as const)("admits existing assignments on an ordinary shared source despite protected %s assignment policy", async (protectedEntity) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "ordinary-source"); + await mkdir(cwd, { recursive: true }); + await writeFile(path.join(cwd, "work.txt"), "Existing assignment files"); + const authorizationPolicy = { assignmentPolicy: { mode: "protected" } }; + await db.insert(companies).values({ id: companyId, name: "Existing assignments", issuePrefix: `A${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Ordinary source" }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Shared source", sourceType: "local_path", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Already assigned", status: "idle", adapterType: "codex_local", adapterConfig: {}, + permissions: protectedEntity === "agent" ? { authorizationPolicy } : {} }); + await db.insert(issues).values({ id: issueId, companyId, title: "Continue authorized assignment", status: "todo", assigneeAgentId: agentId, + executionPolicy: protectedEntity === "task" ? { authorizationPolicy } : null, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "shared_workspace" }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "shared" } } }); + const actor = { type: "agent" as const, agentId, companyId, source: "agent_jwt" as const, onBehalfOfUserId: "responsible-user" }; + expect(await accessService(db).decide({ actor, action: "tasks:assign", resource: { type: "issue", companyId, issueId, assigneeAgentId: agentId }, + scope: { assigneeAgentId: agentId } })).toMatchObject({ allowed: false }); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect(await heartbeat.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const calls = execute.mock.calls.filter(([input]) => input.runId === run!.id); + expect(calls).toHaveLength(1); + expect(await realpath(calls[0]![0].context.paperclipWorkspace.cwd)).toBe(await realpath(cwd)); + expect(await readFile(path.join(cwd, "work.txt"), "utf8")).toBe("Existing assignment files"); + }, 30_000); + + it.each(["project_primary", "adapter_managed", "cloud_sandbox", "git_worktree"] as const)("authorizes isolated source admission by its actual strategy: %s", async (strategy) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "protected-source"); + await mkdir(cwd, { recursive: true }); + const git = (...args: string[]) => execFileSync("git", args, { cwd, stdio: "ignore" }); + git("init", "-b", "main"); + await writeFile(path.join(cwd, "work.txt"), "Protected source content"); + git("add", "."); + git("-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"); + await db.insert(companies).values({ id: companyId, name: "Strategy authorization", issuePrefix: `S${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Protected source", executionWorkspacePolicy: { + authorizationPolicy: { assignmentPolicy: { mode: "protected" } }, + } }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Source", sourceType: "local_path", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Reader", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, title: "Use an isolated source", status: "todo", assigneeAgentId: agentId, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: strategy } }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "managed_isolated" } } }); + const actor = { type: "agent" as const, agentId, companyId, source: "agent_jwt" as const, onBehalfOfUserId: "responsible-user" }; + expect(await accessService(db).decide({ actor, action: "project:read", resource: { type: "project", companyId, projectId } })).toMatchObject({ allowed: true }); + expect(await accessService(db).decide({ actor, action: "tasks:assign", resource: { type: "issue", companyId, projectId, assigneeAgentId: agentId }, + scope: { projectId, assigneeAgentId: agentId } })).toMatchObject({ allowed: false }); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const result = await heartbeat.getRun(run!.id); + const calls = execute.mock.calls.filter(([input]) => input.runId === run!.id); + if (strategy !== "git_worktree") { + expect(result?.status).toBe("failed"); + expect(result?.error).toContain("Target project is protected and requires an explicit assignment grant"); + expect(calls).toHaveLength(0); + } else { + expect(result).toMatchObject({ status: "succeeded", error: null }); + expect(calls).toHaveLength(1); + const workspace = calls[0]![0].context.paperclipWorkspace; + expect(workspace.strategy).toBe("git_worktree"); + expect(await realpath(workspace.cwd)).not.toBe(await realpath(cwd)); + expect(await readFile(path.join(workspace.cwd, "work.txt"), "utf8")).toBe("Protected source content"); + } + expect(await readFile(path.join(cwd, "work.txt"), "utf8")).toBe("Protected source content"); + }, 30_000); + + it.each(["success", "provision_failure", "copy_only_provision", "occupied_target"] as const)("checks nested private source access before worktree copy and retains it after read-grant revocation (%s)", async (scenario) => { + const previousPrivacyMode = process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + try { + if (scenario === "provision_failure") await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + const companyId = randomUUID(), projectId = randomUUID(), privateProjectId = randomUUID(); + const sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "public-source"); + const privateCwd = path.join(cwd, "private"); + const occupiedTarget = path.join(cwd, ".paperclip", "worktrees", "occupied-private"); + await mkdir(privateCwd, { recursive: true }); + const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); + git("init", "-b", "main"); + await writeFile(path.join(privateCwd, "secret.txt"), "NESTED_PRIVATE_TRACKED_BYTES"); + if (scenario === "occupied_target") { + await mkdir(path.join(occupiedTarget, "private"), { recursive: true }); + await writeFile(path.join(occupiedTarget, "private/secret.txt"), "NESTED_PRIVATE_TRACKED_BYTES"); + } + git("add", "."); + git("-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"); + const initialWorktrees = git("worktree", "list", "--porcelain"); + await db.insert(companies).values({ id: companyId, name: "Private copy", issuePrefix: `C${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values([ + { id: projectId, companyId, name: "Public Git source", executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }, + { id: privateProjectId, companyId, name: "Private nested source", visibility: "private" }, + ]); + await db.insert(projectWorkspaces).values([ + { id: sourceId, companyId, projectId, name: "Git source", sourceType: "local_path", cwd, isPrimary: true }, + { id: randomUUID(), companyId, projectId: privateProjectId, name: "Nested private source", sourceType: "local_path", cwd: privateCwd, isPrimary: true }, + ]); + if (scenario === "occupied_target") await db.insert(projectWorkspaces).values({ + id: randomUUID(), companyId, projectId: privateProjectId, name: "Private parent of occupied target", + sourceType: "local_path", cwd: path.dirname(occupiedTarget), isPrimary: false, + }); + await db.insert(agents).values({ id: agentId, companyId, name: "Isolated reader", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + if (scenario === "provision_failure") await db.insert(companyMemberships).values({ companyId, principalType: "agent", principalId: agentId, status: "active", membershipRole: "member" }); + await db.insert(issues).values({ id: issueId, companyId, title: "Read isolated source", status: "todo", assigneeAgentId: agentId, + projectId: scenario === "provision_failure" ? projectId : null, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: "git_worktree", ...(scenario === "provision_failure" ? { provisionCommand: "exit 42" } : {}), ...(scenario === "copy_only_provision" ? { provisionCommand: "touch provision-ran; exit 42" } : {}), ...(scenario === "occupied_target" ? { branchTemplate: "occupied-private", provisionCommand: "touch provision-ran" } : {}) } }, + workspaceSelection: scenario === "provision_failure" ? null : { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "managed_isolated" } } }); + if (scenario === "provision_failure") await db.insert(principalPermissionGrants).values([ + { companyId, principalType: "agent", principalId: agentId, permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }, + { companyId, principalType: "user", principalId: "responsible-user", permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }, + ]); + const admit = async () => { + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + return (await heartbeat.getRun(run!.id))!; + }; + const denied = await admit(); + expect(denied.status).toBe("failed"); + expect(execute.mock.calls.filter(([input]) => input.runId === denied.id)).toHaveLength(0); + expect(git("worktree", "list", "--porcelain")).toBe(initialWorktrees); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].executionWorkspaceId).toBeNull(); + await db.insert(projectAccessMembers).values([ + { companyId, projectId: privateProjectId, subjectType: "agent", subjectId: agentId }, + { companyId, projectId: privateProjectId, subjectType: "user", subjectId: "responsible-user" }, + ]); + expect(await accessService(db).decide({ actor: { type: "agent", agentId, companyId, source: "agent_jwt", onBehalfOfUserId: "responsible-user" }, + action: "tasks:assign", resource: { type: "issue", companyId, projectId, assigneeAgentId: agentId }, scope: { projectId, assigneeAgentId: agentId } })).toMatchObject({ allowed: scenario === "provision_failure" }); + const allowed = await admit(); + const calls = execute.mock.calls.filter(([input]) => input.runId === allowed.id); + let copiedCwd: string; + if (scenario === "provision_failure" || scenario === "occupied_target") { + expect(allowed.status).toBe("failed"); + expect(calls).toHaveLength(0); + if (scenario === "provision_failure") { + expect(allowed.error).toContain("provision"); + const paths = git("worktree", "list", "--porcelain").split("\n").filter(line => line.startsWith("worktree ")).map(line => line.slice(9)); + expect(paths).toHaveLength(2); + copiedCwd = paths.find(entry => entry !== paths[0])!; + } else { + expect(allowed.error).toMatch(/protected|assignment grant/); + expect(git("worktree", "list", "--porcelain")).toBe(initialWorktrees); + copiedCwd = occupiedTarget; + await expect(readFile(path.join(copiedCwd, ".git"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + await expect(readFile(path.join(copiedCwd, "provision-ran"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + } + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].executionWorkspaceId).toBeNull(); + const aliasId = randomUUID(); + await db.insert(executionWorkspaces).values({ id: aliasId, companyId, projectId: null, sourceIssueId: issueId, + name: "Public alias after failed provisioning", mode: "shared_workspace", strategyType: "project_primary", cwd: copiedCwd, + providerType: "local_fs", status: "active" }); + await db.update(issues).set({ executionWorkspaceId: aliasId }).where(eq(issues.id, issueId)); + if (scenario === "occupied_target") { + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId, cwd: copiedCwd, sourceWorkspaceId: aliasId, issueId, + actor: { type: "agent", agentId, companyId, source: "agent_jwt", onBehalfOfUserId: "responsible-user" } })).rejects.toThrow(/protected|assignment grant/); + } + } else { + expect(allowed).toMatchObject({ status: "succeeded", error: null }); + expect(calls).toHaveLength(1); + expect(calls[0]![0].context.paperclipWorkspace.strategy).toBe("git_worktree"); + copiedCwd = calls[0]![0].context.paperclipWorkspace.cwd; + if (scenario === "copy_only_provision") { + await expect(readFile(path.join(copiedCwd, "provision-ran"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + const [bound] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, bound.executionWorkspaceId!)); + expect(workspace.metadata?._controllerProvisionCopyOnly).toBe(true); + } + } + const workspace = { cwd: copiedCwd }; + expect(await realpath(workspace.cwd)).not.toBe(await realpath(cwd)); + expect(await readFile(path.join(workspace.cwd, "private/secret.txt"), "utf8")).toBe("NESTED_PRIVATE_TRACKED_BYTES"); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = { type: "board", userId: "responsible-user", companyIds: [companyId], source: "session", isInstanceAdmin: false }; + next(); + }); + app.use("/api", fileResourceRoutes(db)); + app.use(errorHandler); + const fileUrl = `/api/issues/${issueId}/file-resources/content`; + expect((await request(app).get(fileUrl).query({ workspace: "execution", path: "private/secret.txt" }).expect(200)).body.content.data).toBe("NESTED_PRIVATE_TRACKED_BYTES"); + await db.delete(projectAccessMembers).where(and(eq(projectAccessMembers.projectId, privateProjectId), eq(projectAccessMembers.subjectId, "responsible-user"))); + for (const download of [false, true]) { + const response = await request(app).get(fileUrl).query({ workspace: "execution", path: "private/secret.txt", download }).expect(404); + expect(JSON.stringify(response.body)).not.toContain("NESTED_PRIVATE_TRACKED_BYTES"); + } + expect(await readFile(path.join(workspace.cwd, "private/secret.txt"), "utf8")).toBe("NESTED_PRIVATE_TRACKED_BYTES"); + } finally { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", previousPrivacyMode); + } + }, 40_000); + + it.each([true, false])("handles a deleted configured source with a retained workspace: %s", async (bound) => { + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId, "retained-source"); + await mkdir(cwd, { recursive: true }); + await writeFile(path.join(cwd, "work.txt"), "Retained task work"); + await db.insert(companies).values({ id: companyId, name: "Retained source", issuePrefix: `D${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Source policy" }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Source", sourceType: "local_path", cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Writer", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, title: "Continue retained work", status: "todo", assigneeAgentId: agentId, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "shared_workspace" }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "shared" } } }); + const admit = async () => { + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + return (await heartbeat.getRun(run!.id))!; + }; + let bindingId: string | null = null; + if (bound) { + expect(await admit()).toMatchObject({ status: "succeeded", error: null }); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + bindingId = task.executionWorkspaceId; + expect(bindingId).not.toBeNull(); + } + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, sourceId)); + const result = await admit(); + const calls = execute.mock.calls.filter(([input]) => input.runId === result.id); + if (bound) { + expect(result).toMatchObject({ status: "succeeded", error: null }); + expect(calls).toHaveLength(1); + expect(await realpath(calls[0]![0].context.paperclipWorkspace.cwd)).toBe(await realpath(cwd)); + const [task] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, bindingId!)); + expect(task.executionWorkspaceId).toBe(bindingId); + expect(workspace).toMatchObject({ projectId, projectWorkspaceId: null }); + expect(await readFile(path.join(cwd, "work.txt"), "utf8")).toBe("Retained task work"); + await db.update(projects).set({ visibility: "private" }).where(eq(projects.id, projectId)); + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const callsBeforeRevocation = execute.mock.calls.length; + expect(await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } })).toBeNull(); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + expect(execute.mock.calls).toHaveLength(callsBeforeRevocation); + } else { + expect(result.status).toBe("failed"); + expect(result.error).toContain("Workspace source is unavailable or inaccessible"); + expect(calls).toHaveLength(0); + } + }, 40_000); + it.each([ { code: "workspace_git_scan_timeout", scenario: "temporary", retryable: true }, { code: "workspace_git_scan_saturated", scenario: "temporary", retryable: true }, @@ -212,6 +670,7 @@ suite("task project repository provisioning", () => { await writeFile(path.join(source, "README.md"), "preserved dirty work"); await writeFile(path.join(source, "private.secret"), "must not copy"); await db.insert(companies).values({ id: companyId, name: "Bootstrap recovery", issuePrefix: `R${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); await db.insert(projects).values({ id: projectId, companyId, name: "Local source", status: "in_progress" }); await db.insert(projectWorkspaces).values([ { id: randomUUID(), companyId, projectId, name: "Anchor", sourceType: "local_path", cwd: source, isPrimary: true, createdAt: new Date(Date.now() - 1000) }, @@ -296,6 +755,180 @@ suite("task project repository provisioning", () => { expect(await readFile(path.join(source, "README.md"), "utf8")).toBe("preserved dirty work"); }, 40_000); + it.each(["fresh", "existing", "occupied", "file_url", "absolute_path"] as const)("authorizes a late private configured sibling before %s copy and retains its privacy", async (scenario) => { + const previousPrivacyMode = process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + try { + const companyId = randomUUID(), projectId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const sourceId = randomUUID(), siblingId = randomUUID(), privateIssueId = randomUUID(); + const anchor = path.join(root, companyId, "anchor"), sibling = path.join(root, companyId, "private-sibling"); + for (const cwd of [anchor, sibling]) { + await mkdir(cwd, { recursive: true }); + execFileSync("git", ["init", "-b", "main"], { cwd, stdio: "ignore" }); + await writeFile(path.join(cwd, "README.md"), "public committed content"); + execFileSync("git", ["add", "."], { cwd, stdio: "ignore" }); + execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "seed"], { cwd, stdio: "ignore" }); + } + execFileSync("git", ["remote", "add", "origin", pathToFileURL(anchor).href], { cwd: anchor, stdio: "ignore" }); + const urlOnly = scenario === "file_url" || scenario === "absolute_path"; + await writeFile(path.join(sibling, "private.txt"), "PRIVATE_DIRTY_SIBLING_BYTES"); + if (urlOnly) { + execFileSync("git", ["add", "private.txt"], { cwd: sibling, stdio: "ignore" }); + execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "private source"], { cwd: sibling, stdio: "ignore" }); + await writeFile(path.join(sibling, "uncommitted.txt"), "DO_NOT_OVERLAY_URL_ONLY_SOURCE"); + } + await db.insert(companies).values({ id: companyId, name: "Late sibling", issuePrefix: "L" + companyId.slice(0, 6), defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Read-only source", executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Primary", sourceType: "local_path", cwd: anchor, repoUrl: pathToFileURL(anchor).href, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Reader", adapterType: "codex_local", status: "idle", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: issueId, companyId, title: "Keep task binding", status: "todo", assigneeAgentId: agentId, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: "git_worktree" } }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "managed_isolated" } } }); + const admit = async () => { + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + return (await heartbeat.getRun(run!.id))!; + }; + const initial = await admit(); + expect(initial).toMatchObject({ status: "succeeded", error: null }); + const binding = (await db.select().from(issues).where(eq(issues.id, issueId)))[0].executionWorkspaceId!; + const taskCwd = execute.mock.calls.find(([input]) => input.runId === initial.id)![0].context.paperclipWorkspace.cwd as string; + await db.insert(issues).values({ id: privateIssueId, companyId, title: "Private source task", status: "todo", visibility: "private", responsibleUserId: "private-owner" }); + await db.insert(executionWorkspaces).values({ companyId, projectId: null, sourceIssueId: privateIssueId, name: "Private source files", + mode: "shared_workspace", strategyType: "project_primary", cwd: sibling, providerType: "local_fs", status: "active" }); + const repoUrl = scenario === "absolute_path" ? sibling : pathToFileURL(sibling).href; + const configuredCwd = urlOnly ? null : sibling; + await db.insert(projectWorkspaces).values({ id: siblingId, companyId, projectId, name: "Late sibling", sourceType: "local_path", cwd: configuredCwd, repoUrl, isPrimary: false }); + const key = (scenario === "absolute_path" ? "repo-" : "private-sibling-") + createHash("sha256").update(JSON.stringify([siblingId, repoUrl, null, configuredCwd])).digest("hex").slice(0, 12); + const copy = path.join(taskCwd, ".paperclip-repositories", key); + if (scenario === "existing" || scenario === "occupied") { + await mkdir(path.dirname(copy), { recursive: true }); + if (scenario === "existing") execFileSync("git", ["clone", "--no-hardlinks", sibling, copy], { stdio: "ignore" }); + else await mkdir(copy); + await writeFile(path.join(copy, "private.txt"), "PRESERVED_EXISTING_SIBLING_BYTES"); + } + const denied = await admit(); + expect(denied.status).toBe("failed"); + expect(execute.mock.calls.filter(([input]) => input.runId === denied.id)).toHaveLength(0); + if (scenario === "fresh" || urlOnly) expect(await readdir(path.join(taskCwd, ".paperclip-repositories")).catch(() => [])).toEqual([]); + else expect(await readFile(path.join(copy, "private.txt"), "utf8")).toBe("PRESERVED_EXISTING_SIBLING_BYTES"); + await db.insert(issueAccessGrants).values([ + { issueId: privateIssueId, subjectType: "agent", subjectId: agentId, source: "explicit" }, + { issueId: privateIssueId, subjectType: "user", subjectId: "responsible-user", source: "explicit" }, + ]); + const authorized = await admit(); + expect(authorized.status).toBe(scenario === "occupied" ? "failed" : "succeeded"); + expect(execute.mock.calls.filter(([input]) => input.runId === authorized.id)).toHaveLength(scenario === "occupied" ? 0 : 1); + if (scenario === "occupied") expect(authorized.error).toContain("not a git checkout"); + const expectedBytes = scenario === "fresh" || urlOnly ? "PRIVATE_DIRTY_SIBLING_BYTES" : "PRESERVED_EXISTING_SIBLING_BYTES"; + expect(await readFile(path.join(copy, "private.txt"), "utf8")).toBe(expectedBytes); + if (urlOnly) await expect(readFile(path.join(copy, "uncommitted.txt"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].executionWorkspaceId).toBe(binding); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = { type: "board", userId: "responsible-user", companyIds: [companyId], source: "session", isInstanceAdmin: false }; next(); }); + app.use("/api", fileResourceRoutes(db)); + app.use(errorHandler); + const fileUrl = "/api/issues/" + issueId + "/file-resources/content"; + const relativeFile = path.relative(taskCwd, path.join(copy, "private.txt")); + expect((await request(app).get(fileUrl).query({ workspace: "execution", path: relativeFile }).expect(200)).body.content.data).toBe(expectedBytes); + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, siblingId)); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + for (const download of [false, true]) { + const response = await request(app).get(fileUrl).query({ workspace: "execution", path: relativeFile, download }).expect(404); + expect(JSON.stringify(response.body)).not.toContain(expectedBytes); + } + const deniedContinuation = await admit(); + expect(deniedContinuation.status).toBe("failed"); + expect(execute.mock.calls.filter(([input]) => input.runId === deniedContinuation.id)).toHaveLength(0); + expect(await readFile(path.join(copy, "private.txt"), "utf8")).toBe(expectedBytes); + } finally { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", previousPrivacyMode); + } + }, 60_000); + + it("guards an unbound private file-URL anchor before cloning and retains copied source privacy", async () => { + const previousPrivacyMode = process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + try { + const companyId = randomUUID(), projectId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const sourceId = randomUUID(), privateIssueId = randomUUID(); + const source = path.join(root, companyId, "private-anchor"); + await mkdir(source, { recursive: true }); + execFileSync("git", ["init", "-b", "main"], { cwd: source, stdio: "ignore" }); + await writeFile(path.join(source, "secret.txt"), "PRIVATE_ANCHOR_COMMITTED_BYTES"); + execFileSync("git", ["add", "."], { cwd: source, stdio: "ignore" }); + execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "private source"], { cwd: source, stdio: "ignore" }); + await writeFile(path.join(source, "dirty.txt"), "DO_NOT_OVERLAY_URL_ONLY_ANCHOR"); + await db.insert(companies).values({ id: companyId, name: "Private anchor", issuePrefix: "A" + companyId.slice(0, 6), defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Public project" }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "URL-only anchor", sourceType: "git_repo", cwd: null, repoUrl: pathToFileURL(source).href, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Reader", adapterType: "codex_local", status: "idle", adapterConfig: {}, permissions: {} }); + await db.insert(issues).values({ id: privateIssueId, companyId, title: "Private original", visibility: "private", responsibleUserId: "private-owner" }); + await db.insert(executionWorkspaces).values({ companyId, projectId: null, sourceIssueId: privateIssueId, name: "Private original files", + mode: "shared_workspace", strategyType: "project_primary", cwd: source, providerType: "local_fs", status: "active" }); + await db.insert(issues).values({ id: issueId, companyId, title: "Read anchor", status: "todo", assigneeAgentId: agentId, projectWorkspaceId: sourceId, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "shared" } } }); + const [actualAgent] = await db.select().from(agents).where(eq(agents.id, agentId)); + const [otherAgent] = await db.insert(agents).values({ companyId, name: "Other run owner", adapterType: "codex_local", status: "idle" }).returning(); + const [foreignRun] = await db.insert(heartbeatRuns).values({ companyId, agentId: otherAgent.id, issueId, status: "failed", responsibleUserId: "responsible-user" }).returning(); + const resolver = createHeartbeatWorkspaceResolver(db); + await expect(resolver.resolveWorkspaceForRun(actualAgent, { issueId, executionIdentityRunId: foreignRun.id }, null)) + .rejects.toThrow(/authoritative run identity/); + await db.insert(authUsers).values({ id: "private-owner", name: "Private owner", email: "private-owner@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "private-owner", status: "active", membershipRole: "member" }); + await db.insert(issueAccessGrants).values({ issueId: privateIssueId, subjectType: "agent", subjectId: agentId, source: "explicit" }); + const [ownedRun] = await db.insert(heartbeatRuns).values({ companyId, agentId, issueId, status: "failed", responsibleUserId: "responsible-user" }).returning(); + await expect(resolver.resolveWorkspaceForRun(actualAgent, { issueId, executionIdentityRunId: ownedRun.id, responsibleUserId: "private-owner" }, null)) + .rejects.toThrow(/access|source/i); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + const admit = async () => { + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await heartbeat.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + return (await heartbeat.getRun(run!.id))!; + }; + const denied = await admit(); + expect(denied.status).toBe("failed"); + expect(execute.mock.calls.filter(([input]) => input.runId === denied.id)).toHaveLength(0); + const managedCwd = resolveManagedProjectWorkspaceDir({ companyId, projectId, repoName: "private-anchor" }); + await expect(readdir(managedCwd)).rejects.toMatchObject({ code: "ENOENT" }); + await db.insert(issueAccessGrants).values([ + { issueId: privateIssueId, subjectType: "agent", subjectId: agentId, source: "explicit" }, + { issueId: privateIssueId, subjectType: "user", subjectId: "responsible-user", source: "explicit" }, + ]); + const authorized = await admit(); + expect(authorized).toMatchObject({ status: "succeeded", error: null }); + const copiedCwd = execute.mock.calls.find(([input]) => input.runId === authorized.id)![0].context.paperclipWorkspace.cwd; + expect(await realpath(copiedCwd)).toBe(await realpath(managedCwd)); + expect(await readFile(path.join(copiedCwd, "secret.txt"), "utf8")).toBe("PRIVATE_ANCHOR_COMMITTED_BYTES"); + await expect(readFile(path.join(copiedCwd, "dirty.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = { type: "board", userId: "responsible-user", companyIds: [companyId], source: "session", isInstanceAdmin: false }; next(); }); + app.use("/api", fileResourceRoutes(db)); app.use(errorHandler); + const fileUrl = "/api/issues/" + issueId + "/file-resources/content"; + expect((await request(app).get(fileUrl).query({ workspace: "execution", path: "secret.txt" }).expect(200)).body.content.data).toBe("PRIVATE_ANCHOR_COMMITTED_BYTES"); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + await expect(resolver.resolveWorkspaceForRun(actualAgent, { issueId, executionIdentityRunId: authorized.id, responsibleUserId: "private-owner" }, null)) + .rejects.toThrow(/access|source/i); + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, sourceId)); + await rename(source, source + "-removed"); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssueId)); + for (const download of [false, true]) await request(app).get(fileUrl).query({ workspace: "execution", path: "secret.txt", download }).expect(404); + expect(await readFile(path.join(copiedCwd, "secret.txt"), "utf8")).toBe("PRIVATE_ANCHOR_COMMITTED_BYTES"); + } finally { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", previousPrivacyMode); + } + }, 40_000); + it.each([1, 2])("gives a task all %i repositories without any configured local folders", async (count) => { const companyId = randomUUID(); const projectId = randomUUID(); @@ -313,6 +946,7 @@ suite("task project repository provisioning", () => { repositoryRows.push({ id: randomUUID(), companyId, projectId, name: `Repo ${index}`, sourceType: "git_repo", repoUrl: pathToFileURL(source).href, cwd: null, isPrimary: index === 0 }); } await db.insert(companies).values({ id: companyId, name: "Repo test", issuePrefix: `R${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); await db.insert(projects).values({ id: projectId, companyId, name: "Multi-repo", status: "in_progress" }); await db.insert(projectWorkspaces).values(repositoryRows); await db.insert(agents).values({ id: agentId, companyId, name: "Test", role: "engineer", status: "idle", adapterType: "codex_local", adapterConfig: {}, runtimeConfig: {}, permissions: {} }); diff --git a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts index 54bcebe2005..39592ac031e 100644 --- a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts +++ b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { randomUUID } from "node:crypto"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; @@ -8,6 +9,7 @@ import { agentRuntimeState, agentWakeupRequests, companies, + authUsers, companyMemberships, companySkills, createDb, @@ -98,7 +100,7 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { } return baseExecute(); }); - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { mockAdapterExecute.mockClear(); @@ -121,6 +123,7 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { await db.delete(companySkills); await db.delete(companyMemberships); await db.delete(companies); + await db.delete(authUsers); }); afterAll(async () => { @@ -138,6 +141,10 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { issuePrefix: `R${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`, defaultResponsibleUserId: ownerUserId, }); + await db.insert(authUsers).values({ + id: ownerUserId, name: "Company owner", email: `${ownerUserId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }); await db.insert(companyMemberships).values({ companyId, principalType: "user", @@ -184,6 +191,10 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { it("dispatches an interrupted queue under the clicking operator through the real startup path", async () => { const { companyId, agentId, ownerUserId } = await seedCompany(); const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(authUsers).values({ + id: operatorId, name: "Clicking operator", email: `${operatorId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }); await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, membershipRole: "operator", status: "active" }); await db.insert(issues).values({ id: issueId, companyId, title: "Interrupted queue", status: "todo", @@ -213,6 +224,10 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { it("keeps a board manual wake under its caller even when it adopts someone else's queue", async () => { const { companyId, agentId, ownerUserId } = await seedCompany(); const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(authUsers).values({ + id: operatorId, name: "Clicking operator", email: `${operatorId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }); await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, membershipRole: "operator", status: "active" }); await db.insert(issues).values({ id: issueId, companyId, title: "Manual wake", status: "todo", @@ -236,6 +251,10 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { it("keeps the clicking user when a manual wake merges into an older deferred receipt", async () => { const { companyId, agentId, ownerUserId } = await seedCompany(); const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(authUsers).values({ + id: operatorId, name: "Clicking operator", email: `${operatorId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }); await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, membershipRole: "operator", status: "active" }); await db.insert(issues).values({ id: issueId, companyId, title: "Deferred manual wake", status: "todo", @@ -273,6 +292,10 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { it("starts an unscoped manual wake with its own user instead of joining another user's run", async () => { const { companyId, agentId, ownerUserId } = await seedCompany(); const operatorId = `operator-${randomUUID()}`; + await db.insert(authUsers).values({ + id: operatorId, name: "Clicking operator", email: `${operatorId}@example.test`, + createdAt: new Date(), updatedAt: new Date(), + }); await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, membershipRole: "operator", status: "active" }); let finish!: () => void; @@ -321,6 +344,8 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { const { companyId, agentId } = await seedCompany(); const issueResponsibleUserId = `issue-owner-${randomUUID()}`; const commenterUserId = `commenter-${randomUUID()}`; + await db.insert(authUsers).values([issueResponsibleUserId, commenterUserId].map(id => ({ id, name: "Fixture user", email: `${id}@example.test`, createdAt: new Date(), updatedAt: new Date() }))); + await db.insert(companyMemberships).values([issueResponsibleUserId, commenterUserId].map(principalId => ({ companyId, principalType: "user" as const, principalId, status: "active", membershipRole: "member" }))); const issueId = randomUUID(); await db.insert(issues).values({ id: issueId, @@ -377,6 +402,8 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { const { companyId, agentId } = await seedCompany(); const issueResponsibleUserId = `issue-owner-${randomUUID()}`; const commenterUserId = `commenter-${randomUUID()}`; + await db.insert(authUsers).values([issueResponsibleUserId, commenterUserId].map(id => ({ id, name: "Fixture user", email: `${id}@example.test`, createdAt: new Date(), updatedAt: new Date() }))); + await db.insert(companyMemberships).values([issueResponsibleUserId, commenterUserId].map(principalId => ({ companyId, principalType: "user" as const, principalId, status: "active", membershipRole: "member" }))); const issueId = randomUUID(); const commentId = randomUUID(); await db.insert(issues).values({ diff --git a/server/src/__tests__/heartbeat-session-preparation.test.ts b/server/src/__tests__/heartbeat-session-preparation.test.ts index 7460a457338..9bacb2c7fb1 100644 --- a/server/src/__tests__/heartbeat-session-preparation.test.ts +++ b/server/src/__tests__/heartbeat-session-preparation.test.ts @@ -1,7 +1,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { agents, agentTaskSessions, companies, createDb, heartbeatRuns, issues } from "@paperclipai/db"; +import { agents, agentTaskSessions, authUsers, companies, companyMemberships, createDb, heartbeatRuns, issues } from "@paperclipai/db"; import { registerServerAdapter, unregisterServerAdapter, type AdapterExecutionContext } from "../adapters/index.js"; import { heartbeatService } from "../services/heartbeat.js"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; @@ -43,10 +43,12 @@ describePostgres("heartbeat session preparation through adapter dispatch", () => }); async function seed(runtimeConfig: Record = {}) { - const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), userId = randomUUID(); await db.insert(companies).values({ id: companyId, name: "Session fixture", issuePrefix: `S${companyId.slice(0, 6)}` }); + await db.insert(authUsers).values({ id: userId, name: "Session owner", email: `${userId}@example.test`, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, name: "Session reader", adapterType, status: "idle", adapterConfig: { model: "model-a" }, runtimeConfig }); - await db.insert(issues).values({ id: issueId, companyId, title: "Session fixture", status: "todo", assigneeAgentId: agentId, responsibleUserId: "session-user", issueNumber: 1 }); + await db.insert(issues).values({ id: issueId, companyId, title: "Session fixture", status: "todo", assigneeAgentId: agentId, responsibleUserId: userId, issueNumber: 1 }); return { companyId, agentId, issueId }; } diff --git a/server/src/__tests__/heartbeat-slack-invitation.test.ts b/server/src/__tests__/heartbeat-slack-invitation.test.ts index 2e168b1ec63..2a6466e382c 100644 --- a/server/src/__tests__/heartbeat-slack-invitation.test.ts +++ b/server/src/__tests__/heartbeat-slack-invitation.test.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { - agents, chatConversations, chatDeliveries, chatEndpointResources, + agents, authUsers, chatConversations, chatDeliveries, chatEndpointResources, chatEndpoints, chatExternalPrincipals, chatIdentityLinks, chatMessageLinks, companies, companyMemberships, createDb, issueComments, issues, toolApplications, toolConnections, @@ -55,6 +55,7 @@ describePostgres("saved Slack invitation command in heartbeat prompts", () => { const endpointId = randomUUID(), resourceId = randomUUID(), conversationId = randomUUID(); const applicationId = randomUUID(), connectionId = randomUUID(), principalId = randomUUID(); const userId = `invitation-user-${randomUUID()}`; + await db.insert(authUsers).values({ id: userId, name: "Invitation user", email: `${userId}@example.test`, createdAt: new Date(), updatedAt: new Date() }); await db.insert(companies).values({ id: companyId, name: "Invitation fixture", issuePrefix: `I${companyId.slice(0, 6)}` }); await db.insert(agents).values({ id: agentId, companyId, name: "Original researcher", adapterType, status: "idle", @@ -105,7 +106,8 @@ describePostgres("saved Slack invitation command in heartbeat prompts", () => { const f = await seed(); for (let index = 0; index < 2; index++) { const input = await turn(f); - expect(input.runtime.sessionId).toBe(index === 0 ? null : "invitation-session"); + const completed = await heartbeat.getRun(input.runId); + expect(input.runtime.sessionId, JSON.stringify(completed?.resultJson?.configFreshness)).toBe(index === 0 ? null : "invitation-session"); const context = input.context; for (const key of ["paperclipTaskMarkdown", "paperclipTaskMarkdownCompact", "paperclipTaskMarkdownAssignment", "paperclipTaskMarkdownAssignmentCompact"]) { const prompt = String(context[key]); diff --git a/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts b/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts index 9759f5a995e..e81f9d3c5ef 100644 --- a/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts +++ b/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { execFile } from "node:child_process"; import { createHash, randomUUID } from "node:crypto"; import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises"; @@ -14,6 +15,8 @@ import { agentWakeupRequests, agents, companies, + authUsers, + companyMemberships, companySkills, createDb, closeRegisteredClients, @@ -342,6 +345,8 @@ async function seedBranchContainmentRun( createdAt: now, updatedAt: now, }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(projects).values({ id: projectId, companyId, @@ -880,7 +885,7 @@ describeEmbeddedPostgres("heartbeat workspace branch containment", () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-branch-containment-"); db = createDb(tempDb.connectionString); } - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { // Await every in-flight background heartbeat run to quiescence before the @@ -918,16 +923,18 @@ describeEmbeddedPostgres("heartbeat workspace branch containment", () => { await deleteHeartbeatRunsForCleanup(db); await db.delete(issueComments); await db.delete(issues); + await db.delete(executionWorkspaces); await db.delete(projectWorkspaces); await db.delete(projects); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); await db.delete(agents); await db.delete(workspaceOperations); - await db.delete(executionWorkspaces); await db.delete(environments); await db.delete(companySkills); + await db.delete(companyMemberships); await db.delete(companies); + await db.delete(authUsers).where(eq(authUsers.id, "responsible-user")); vi.unstubAllEnvs(); }); @@ -972,6 +979,8 @@ describeEmbeddedPostgres("heartbeat workspace branch containment", () => { }; await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); await db.insert(companies).values({ id: companyId, name: "Retained source", issuePrefix, status: "active", defaultResponsibleUserId: "responsible-user" }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(projects).values({ id: projectId, companyId, name: "Repository replacement", status: "active", executionWorkspacePolicy: { enabled: true, defaultMode: "isolated_workspace", workspaceStrategy: { type: "git_worktree", baseRef: "HEAD" } } }); await db.insert(projectWorkspaces).values([ @@ -1039,19 +1048,19 @@ describeEmbeddedPostgres("heartbeat workspace branch containment", () => { await heartbeat.resumeQueuedRuns(); const run = await waitForRunToFinish(heartbeat, seeded.runId); await heartbeat.waitForRunExecutionDrain(seeded.runId); - expect(run).toMatchObject({ status: "failed", errorCode: "workspace_validation_failed" }); - expect(run?.resultJson).toMatchObject({ workspaceValidation: { reason: "persisted_workspace_source_conflict", - reasonCode: kind === "explicit_conflict" ? "explicit_project_workspace_conflict" - : kind === "unproven" ? "source_registration_unproven" : "source_scope_mismatch" } }); + if (kind === "foreign_company") { + expect(run).toMatchObject({ status: "failed", errorCode: "setup_failed", error: "Task workspace access is no longer available" }); + } else { + expect(run).toMatchObject({ status: "failed", errorCode: "workspace_validation_failed" }); + expect(run?.resultJson).toMatchObject({ workspaceValidation: { reason: "persisted_workspace_source_conflict", + reasonCode: kind === "explicit_conflict" ? "explicit_project_workspace_conflict" : "source_registration_unproven" } }); + } expect(adapterExecute).not.toHaveBeenCalled(); const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, seeded.executionWorkspaceId)); expect(workspace).toMatchObject({ projectWorkspaceId: null, cwd: seeded.worktree, providerRef: seeded.worktree, branchName: seeded.branch, metadata: seeded.metadata }); const [issue] = await db.select().from(issues).where(eq(issues.id, seeded.issueId)); expect(issue).toMatchObject({ executionWorkspaceId: seeded.executionWorkspaceId, projectWorkspaceId: kind === "explicit_conflict" ? seeded.newWorkspaceId : null }); - // Intentionally malformed cross-company/project FKs also prevent the - // existing issue-update service from changing status. The source gate - // still must fail before dispatch and may never silently repair that FK. if (kind === "explicit_conflict" || kind === "unproven") expect(issue.status).toBe("blocked"); expect(await readFile(path.join(seeded.worktree, "retained.txt"), "utf8")).toBe("unexported original task edits\n"); expect(await readFile(path.join(seeded.replacement, "replacement.txt"), "utf8")).toBe("replacement source must stay untouched\n"); @@ -1073,6 +1082,8 @@ describeEmbeddedPostgres("heartbeat workspace branch containment", () => { status: "active", defaultResponsibleUserId: "responsible-user", }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(agents).values({ id: agentId, companyId, diff --git a/server/src/__tests__/heartbeat-workspace-busy.test.ts b/server/src/__tests__/heartbeat-workspace-busy.test.ts index cb45daa7445..0b99df28caf 100644 --- a/server/src/__tests__/heartbeat-workspace-busy.test.ts +++ b/server/src/__tests__/heartbeat-workspace-busy.test.ts @@ -7,11 +7,13 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest import { costEvents, agents, + authUsers, agentRuntimeState, agentWakeupRequests, activityLog, budgetPolicies, companies, + companyMemberships, companySkills, createDb, closeRegisteredClients, @@ -202,6 +204,7 @@ describeEmbeddedPostgres("shared-workspace run serialization", () => { await db.delete(agents); await db.delete(environments); await db.delete(companySkills); + await db.delete(companyMemberships); await db.delete(companies); } @@ -282,6 +285,14 @@ describeEmbeddedPostgres("shared-workspace run serialization", () => { requireBoardApprovalForNewAgents: false, defaultResponsibleUserId: "responsible-user", }); + await db.insert(authUsers).values({ + id: "responsible-user", name: "Responsible user", email: "workspace-busy@example.test", + createdAt: now, updatedAt: now, + }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ + companyId, principalType: "user", principalId: "responsible-user", + status: "active", membershipRole: "member", + }); await db.insert(projects).values({ id: projectId, @@ -611,7 +622,7 @@ describeEmbeddedPostgres("shared-workspace run serialization", () => { expect(retryRuns).toHaveLength(0); }); - it("allow passes the busy gate for a sandbox environment and adds coordination context", async () => { + it("rejects unproven concurrent physical sharing in a sandbox environment", async () => { const fixture = await seedWorkspaceFixture({ issueWorkspaceSettings: { sharedWorkspaceConcurrency: "allow" }, agentEnvironmentDriver: "sandbox", @@ -626,11 +637,8 @@ describeEmbeddedPostgres("shared-workspace run serialization", () => { expect(run).not.toBeNull(); const finishedRun = await waitForRunToLeaveActiveStates(run!.id); - expect(finishedRun?.errorCode).not.toBe(WORKSPACE_BUSY_ERROR_CODE); - expect(executedRunIds).toContain(run!.id); - expect((finishedRun?.contextSnapshot as Record)?.paperclipTaskMarkdown).toContain( - `shared workspace is concurrently held by run ${fixture.holderRunId}`, - ); + expect(executedRunIds).not.toContain(run!.id); + expect(finishedRun?.error).toContain("cannot place concurrent agents in one physical shared folder"); const retryRuns = await db .select({ id: heartbeatRuns.id }) .from(heartbeatRuns) diff --git a/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts b/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts index 545b3a16305..632fe3273ce 100644 --- a/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts +++ b/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { execFile } from "node:child_process"; import { randomUUID } from "node:crypto"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; @@ -14,6 +15,8 @@ import { agentWakeupRequests, agents, companies, + authUsers, + companyMemberships, companySkills, createDb, documentRevisions, @@ -166,6 +169,8 @@ async function seedRunTarget(db: Db, repoRoot: string) { createdAt: new Date(), updatedAt: new Date(), }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db.insert(projects).values({ id: projectId, companyId, @@ -265,7 +270,7 @@ describeEmbeddedPostgres("heartbeat workspace finalization branch guard", () => beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-finalize-branch-"); db = createDb(tempDb.connectionString); - }, 20_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterEach(async () => { // Await every in-flight background heartbeat run to quiescence before the @@ -299,15 +304,17 @@ describeEmbeddedPostgres("heartbeat workspace finalization branch guard", () => await deleteHeartbeatRowsAfterActivityLogDrains(db); await db.delete(issueComments); await db.delete(issues); + await db.delete(executionWorkspaces); await db.delete(projectWorkspaces); await db.delete(projects); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); await db.delete(agents); - await db.delete(executionWorkspaces); await db.delete(environments); await db.delete(companySkills); + await db.delete(companyMemberships); await db.delete(companies); + await db.delete(authUsers).where(eq(authUsers.id, "responsible-user")); }); afterAll(async () => { diff --git a/server/src/__tests__/heartbeat-workspace-session.test.ts b/server/src/__tests__/heartbeat-workspace-session.test.ts index 906a7d23c6e..9f1d856959e 100644 --- a/server/src/__tests__/heartbeat-workspace-session.test.ts +++ b/server/src/__tests__/heartbeat-workspace-session.test.ts @@ -37,7 +37,6 @@ import { resolveWorkspaceAfterLowTrustPreflight, resolveRuntimeSessionParamsForWorkspace, shouldDeferFollowupWakeForSameIssue, - stripHostWorkspaceProvisionForLowTrustSandbox, stripWorkspaceRuntimeFromExecutionRunConfig, shouldResetTaskSessionForModelChange, stripConfiguredModelFromSessionParams, @@ -49,6 +48,7 @@ import { buildAnchorFallbackWorkspaceNotes, type ResolvedWorkspaceForRun, } from "../services/heartbeat.ts"; +import { stripHostWorkspaceProvision } from "../services/heartbeat/workspaces.js"; import type { TrustPresetResolution } from "../services/trust-preset-resolver.ts"; const execFile = promisify(execFileCallback); @@ -250,19 +250,19 @@ function buildIssueAncestryDb(rows: Array<{ id: string; companyId: string; paren } describe("assertGitSensitiveAdapterWorkspaceValid", () => { - it("rejects a project-workspace-linked issue that is missing its project id before adapter launch", async () => { - await expectWorkspaceValidationFailure( - buildWorkspaceValidationInput({ - issue: { - id: "issue-1", - identifier: "PAP-1", - projectId: null, - projectWorkspaceId: "workspace-1", - }, - }), - "missing_project_id", - "linked to a project workspace but has no project id", - ); + it("accepts a projectless issue using an authorized persisted source workspace", async () => { + await expect(assertGitSensitiveAdapterWorkspaceValid(buildWorkspaceValidationInput({ + issue: { id: "issue-1", identifier: "PAP-1", projectId: null, projectWorkspaceId: "workspace-1" }, + resolvedWorkspace: buildResolvedWorkspace({ localPathOnlyWorkspace: true }), + }))).resolves.toBeUndefined(); + }); + + it("rejects a configured source without source-project evidence", async () => { + const input = buildWorkspaceValidationInput(); + await expectWorkspaceValidationFailure(buildWorkspaceValidationInput({ + issue: { id: "issue-1", identifier: "PAP-1", projectId: null, projectWorkspaceId: "workspace-1" }, + persistedExecutionWorkspace: { ...input.persistedExecutionWorkspace!, projectId: null }, + }), "missing_project_id", "linked to a project workspace but has no project id"); }); it("rejects a git-sensitive local adapter when effective cwd differs from the persisted workspace cwd", async () => { @@ -1125,8 +1125,15 @@ describe("requiresPushCapabilityPreflight", () => { }); }); -describe("stripHostWorkspaceProvisionForLowTrustSandbox", () => { - it("removes only the host-side provision command for sandbox-backed low-trust runs", () => { +describe("stripHostWorkspaceProvision", () => { + it.each(["sandbox", "ssh"])("removes nested and top-level runtime commands immutably for %s", selectedEnvironmentDriver => { + const config = { runtimeProvisionCommand: "top-level", workspaceStrategy: { type: "git_worktree", provisionCommand: "host", runtimeProvisionCommand: "nested" } }; + const original = structuredClone(config); + expect(stripHostWorkspaceProvision({ config, skipHostProvision: true })) + .toEqual({ workspaceStrategy: { type: "git_worktree" } }); + expect(config).toEqual(original); + }); + it("removes controller provisioning while retaining unrelated settings", () => { const config = { workspaceStrategy: { type: "git_worktree", @@ -1139,10 +1146,9 @@ describe("stripHostWorkspaceProvisionForLowTrustSandbox", () => { }, }; - const result = stripHostWorkspaceProvisionForLowTrustSandbox({ + const result = stripHostWorkspaceProvision({ config, - trustPreset: lowTrustResolution(), - selectedEnvironmentDriver: "sandbox", + skipHostProvision: true, }); expect(result).not.toBe(config); @@ -1155,7 +1161,7 @@ describe("stripHostWorkspaceProvisionForLowTrustSandbox", () => { expect(config.workspaceStrategy.provisionCommand).toBe("bash ./scripts/provision-worktree.sh"); }); - it("preserves provision commands for standard-trust runs", () => { + it("strips provision commands when the controller is not the execution target", () => { const config = { workspaceStrategy: { type: "git_worktree", @@ -1163,14 +1169,13 @@ describe("stripHostWorkspaceProvisionForLowTrustSandbox", () => { }, }; - expect(stripHostWorkspaceProvisionForLowTrustSandbox({ + expect(stripHostWorkspaceProvision({ config, - trustPreset: standardTrustResolution(), - selectedEnvironmentDriver: "sandbox", - })).toBe(config); + skipHostProvision: true, + })).toEqual({ workspaceStrategy: { type: "git_worktree" } }); }); - it("preserves provision commands when a low-trust run is not sandbox-backed", () => { + it("preserves provision commands when the server permits controller provisioning", () => { const config = { workspaceStrategy: { type: "git_worktree", @@ -1178,10 +1183,9 @@ describe("stripHostWorkspaceProvisionForLowTrustSandbox", () => { }, }; - expect(stripHostWorkspaceProvisionForLowTrustSandbox({ + expect(stripHostWorkspaceProvision({ config, - trustPreset: lowTrustResolution(), - selectedEnvironmentDriver: "local", + skipHostProvision: false, })).toBe(config); }); }); @@ -2468,6 +2472,7 @@ describe("effective run session config freshness", () => { workspaceConfig: { requestedMode: "shared_workspace", effectiveMode: "shared_workspace", + issueSettings: null, reusableExecutionWorkspaceConfig: null, existingExecutionWorkspace: null, }, @@ -2476,6 +2481,7 @@ describe("effective run session config freshness", () => { workspaceConfig: { requestedMode: "shared_workspace", effectiveMode: "shared_workspace", + issueSettings: { mode: "shared_workspace" }, reusableExecutionWorkspaceConfig: { strategyType: "project_primary", workspaceGeneration: 1, diff --git a/server/src/__tests__/heartbeat-workspace-source-trust.test.ts b/server/src/__tests__/heartbeat-workspace-source-trust.test.ts new file mode 100644 index 00000000000..83406d46967 --- /dev/null +++ b/server/src/__tests__/heartbeat-workspace-source-trust.test.ts @@ -0,0 +1,230 @@ +import express from "express"; +import request from "supertest"; +import { fileResourceRoutes } from "../routes/file-resources.js"; +import { errorHandler } from "../middleware/index.js"; +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { createHash, randomUUID } from "node:crypto"; +import { and, eq } from "drizzle-orm"; +import { execFileSync } from "node:child_process"; +import { mkdtemp, mkdir, writeFile, readFile, realpath, readdir, rename, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { agents, authUsers, companies, companyMemberships, createDb, environments, executionWorkspaces, heartbeatRuns, issues, issueAccessGrants, principalPermissionGrants, projects, projectAccessMembers, projectWorkspaces } from "@paperclipai/db"; +import { setExpensiveWorkspaceGitExecutor } from "@paperclipai/adapter-utils/git-workspace-sync"; +import { buildProjectMentionHref } from "@paperclipai/shared"; +import { createWorkspaceGitOperationScheduler, WorkspaceGitScanError } from "../services/workspace-git-operation-scheduler.js"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; +import { heartbeatService } from "../services/heartbeat.ts"; +import { createHeartbeatWorkspaceResolver } from "../services/heartbeat/workspaces.js"; +import { resolveManagedProjectWorkspaceDir, resolvePaperclipInstanceRoot } from "../home-paths.js"; +import { issueService } from "../services/issues.js"; +import { accessService } from "../services/access.js"; +import { assertExecutionWorkspaceRootWriteAccess } from "../services/execution-workspace-repositories.js"; +import { instanceSettingsService } from "../services/instance-settings.ts"; +import { environmentRuntimeService } from "../services/environment-runtime.js"; +import { findNativeChatWorkspaceScope } from "../services/native-runtime/native-chat-workspace.js"; +import { drainHeartbeatRunsToQuiescence } from "./helpers/drain-heartbeat-runs.js"; + +const execute = vi.hoisted(() => vi.fn(async (_input: any) => ({ exitCode: 0, signal: null, timedOut: false }))); +vi.mock("../adapters/index.js", () => ({ + getServerAdapter: () => ({ type: "codex_local", execute, supportsLocalAgentJwt: false }), + findActiveServerAdapter: () => ({ type: "codex_local", execute, supportsLocalAgentJwt: false }), + runningProcesses: new Map(), +})); + +const support = await getEmbeddedPostgresTestSupport(); +const suite = support.supported ? describe : describe.skip; +suite("heartbeat physical workspace source trust", () => { + let database: Awaited>; + let db: ReturnType; + let root: string; + let heartbeat: ReturnType; + beforeAll(async () => { + root = await mkdtemp(path.join(os.tmpdir(), "paperclip-project-repos-")); + vi.stubEnv("PAPERCLIP_HOME", path.join(root, "home")); + vi.stubEnv("PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC", "false"); + database = await startEmbeddedPostgresTestDatabase("project-repositories"); + db = createDb(database.connectionString); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture owner", email: "owner@example.test", createdAt: new Date(), updatedAt: new Date() }); + heartbeat = heartbeatService(db); + execute.mockImplementation(async (input) => { + await db.update(issues).set({ status: "done" }).where(eq(issues.id, input.context.issueId)); + return { exitCode: 0, signal: null, timedOut: false }; + }); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterAll(async () => { + if (db && heartbeat) await drainHeartbeatRunsToQuiescence(db, heartbeat); + await db?.$client.end({ timeout: 5 }); + await database?.cleanup(); + vi.unstubAllEnvs(); + await rm(root, { recursive: true, force: true }); + }, 60_000); + afterEach(async () => { + await drainHeartbeatRunsToQuiescence(db, heartbeat); + setExpensiveWorkspaceGitExecutor(null); + vi.stubEnv("PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC", "false"); + await instanceSettingsService(db).updateExperimental({ + enableIsolatedWorkspaces: false, + enableIsolatedWorkspacesByDefault: false, + }); + }); + + it.each(["current", "retained", "sandbox", "provision_current", "provision_retained"] as const)("enforces physical source containment before dispatch (%s)", async scenario => { + const sandbox = scenario === "sandbox"; + const provision = scenario.startsWith("provision_"); + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + const cwd = path.join(root, companyId, "low-trust-source"); + await mkdir(cwd, { recursive: true }); + await writeFile(path.join(cwd, "work.txt"), "Low-trust source files"); + const base = path.join(root, companyId, "base"); + if (provision || sandbox) { + await mkdir(base, { recursive: true }); + await writeFile(path.join(base, "source.txt"), "source"); + if (sandbox) { + await mkdir(path.join(base, "scripts")); + await writeFile(path.join(base, "scripts", "provision-worktree.sh"), `if [ "$FIXTURE_SANDBOX_EXECUTION" != 1 ]; then printf host-provisioned > '${path.join(cwd, "provision-ran")}'; fi\n`); + } + const git = (...args: string[]) => execFileSync("git", args, { cwd: base, stdio: "ignore" }); + git("init", "-b", "main"); git("add", "."); + git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"); + await rm(cwd, { recursive: true }); + git("worktree", "add", "-b", "task", cwd); + } + const provisionCommand = `printf host-provisioned > '${path.join(cwd, "provision-ran")}'`; + await db.insert(companies).values({ id: companyId, name: "Physical trust", issuePrefix: `F${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Low-trust folder owner", executionWorkspacePolicy: { + trustPreset: "low_trust_review", authorizationPolicy: { trustPreset: "low_trust_review", trustBoundary: { mode: "low_trust_review", companyId, rootIssueId: issueId, projectIds: [projectId] } }, + } }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Source", sourceType: "local_path", cwd: sandbox ? base : cwd, isPrimary: true }); + await db.insert(agents).values({ id: agentId, companyId, name: "Standard local worker", status: "idle", adapterType: "codex_local", adapterConfig: {}, permissions: {} }); + await db.insert(companyMemberships).values({ companyId, principalType: "agent", principalId: agentId, status: "active", membershipRole: "member" }); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Projectless alias", cwd, projectId: sandbox ? projectId : null, mode: sandbox ? "isolated_workspace" : "shared_workspace", strategyType: sandbox ? "git_worktree" : "project_primary", + ...(sandbox ? { projectWorkspaceId: sourceId, providerRef: cwd, branchName: "task", baseRef: "main", config: { provisionCommand: "bash ./scripts/provision-worktree.sh" } } : {}), + ...(provision ? { branchName: "task", baseRef: "main", config: { provisionCommand } } : {}) }).returning(); + await db.insert(issues).values({ id: issueId, companyId, title: "Standard task", status: "todo", assigneeAgentId: agentId, + executionWorkspaceId: workspace.id, projectWorkspaceId: sandbox ? sourceId : null, executionWorkspacePreference: "reuse_existing", executionWorkspaceSettings: { mode: sandbox || provision ? "isolated_workspace" : "shared_workspace", + ...(provision ? { workspaceStrategy: { type: "git_worktree", provisionCommand } } : {}) }, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "existing", workspaceId: workspace.id } } }); + if (scenario === "retained" || scenario === "provision_retained") { + const { projectService } = await import("../services/projects.js"); + await projectService(db).updateWorkspace(projectId, sourceId, { cwd: path.join(root, companyId, "relocated") }); + } + let driver = heartbeat; + if (sandbox) { + const environmentId = randomUUID(); + await db.insert(environments).values({ id: environmentId, name: "Source sandbox", driver: "sandbox", status: "active", config: { provider: "fake", image: "fake:test" } }); + await db.update(agents).set({ defaultEnvironmentId: environmentId }).where(eq(agents.id, agentId)); + driver = heartbeatService(db, { environmentRuntime: { ...environmentRuntimeService(db), execute: async input => ({ + exitCode: 0, stdout: execFileSync(input.command, input.args ?? [], { cwd: root, input: input.stdin, encoding: "utf8", env: { ...process.env, ...input.env, FIXTURE_SANDBOX_EXECUTION: "1" } }), + stderr: "", signal: null, timedOut: false, + }) } }); + } + const run = await driver.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await driver.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, driver); + if (sandbox) { + expect(await driver.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }); + const calls = execute.mock.calls.filter(([input]) => input.runId === run!.id); + expect(calls).toHaveLength(1); + expect(calls[0][0].context.paperclipEnvironment.driver).toBe("sandbox"); + await expect(readFile(path.join(cwd, "provision-ran"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + } else { + expect(await driver.getRun(run!.id)).toMatchObject({ status: "failed", error: expect.stringContaining("Physical workspace source trust is incompatible") }); + expect(execute.mock.calls.filter(([input]) => input.runId === run!.id)).toHaveLength(0); + if (provision) await expect(readFile(path.join(cwd, "provision-ran"), "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + } + }); + + it.each([ + ["configured", "none", "sandbox"], ["builtin", "none", "sandbox"], + ["configured", "agent", "sandbox"], ["builtin", "agent", "sandbox"], + ["configured", "both", "sandbox"], ["builtin", "both", "sandbox"], + ["configured", "both", "local"], ["builtin", "both", "local"], + ["configured", "none", "local"], ["builtin", "none", "local"], + ["configured", "both", "local_default"], ["builtin", "both", "local_default"], + ["configured", "both", "local_new_default"], ["builtin", "both", "local_new_default"], + ["configured", "both", "local_new_primary"], ["builtin", "both", "local_new_primary"], + ] as const)("places standard source %s provisioning with %s grants on %s", async (provisionKind, grants, placement) => { + const implicitDefault = placement.startsWith("local_"); + const newerDefault = placement === "local_new_default" || placement === "local_new_primary"; + const companyId = randomUUID(), projectId = randomUUID(), sourceId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + const cwd = path.join(root, companyId, "read-only-source"); + const marker = path.join(root, companyId, "host-script-ran"); + await mkdir(path.join(cwd, "scripts"), { recursive: true }); + await writeFile(path.join(cwd, "source.txt"), "read-only source bytes"); + await writeFile(path.join(cwd, "scripts", "provision-worktree.sh"), `printf host-script-ran > '${marker}'\n`); + const git = (...args: string[]) => execFileSync("git", args, { cwd, stdio: "ignore" }); + git("init", "-b", "main"); git("add", "."); + git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"); + git("remote", "add", "origin", pathToFileURL(cwd).href); + await db.insert(companies).values({ id: companyId, name: "Read-only source", issuePrefix: `R${companyId.slice(0, 6)}`, defaultResponsibleUserId: "responsible-user" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "owner" }); + await db.insert(projects).values({ id: projectId, companyId, name: "Protected source", executionWorkspacePolicy: { + authorizationPolicy: { assignmentPolicy: { mode: "protected" } }, + ...(placement === "local_new_default" ? { defaultProjectWorkspaceId: sourceId } : {}), + } }); + if (newerDefault) await db.insert(projectWorkspaces).values({ id: randomUUID(), companyId, projectId, name: "Older source", sourceType: "local_path", cwd: path.join(root, companyId, "older-source"), isPrimary: placement === "local_new_default", createdAt: new Date(0) }); + await db.insert(projectWorkspaces).values({ id: sourceId, companyId, projectId, name: "Source", sourceType: "local_path", cwd, repoUrl: pathToFileURL(cwd).href, isPrimary: placement !== "local_new_default" }); + await db.insert(agents).values({ id: agentId, companyId, name: "Reader", status: "idle", adapterType: "codex_local", adapterConfig: { fullAuto: grants === "both" }, permissions: {} }); + await db.insert(companyMemberships).values({ companyId, principalType: "agent", principalId: agentId, status: "active", membershipRole: "member" }); + const taskInput = { id: issueId, companyId, projectId: implicitDefault ? projectId : null, title: "Read-only isolated source", status: "todo" as const, assigneeAgentId: agentId, + projectWorkspaceId: sourceId, executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: "git_worktree", + worktreeParentDir: path.join(root, companyId, "worktrees"), ...(provisionKind === "configured" ? { provisionCommand: `printf host-script-ran > '${marker}'` } : {}) } }, + workspaceSelection: implicitDefault ? null : { version: 1 as const, source: "explicit" as const, selection: { kind: "configured_source" as const, projectWorkspaceId: sourceId, mode: "managed_isolated" as const } } }; + if (newerDefault) { + const { projectWorkspaceId: _source, workspaceSelection: _selection, ...createInput } = taskInput; + const created = await issueService(db).create(companyId, createInput); + expect(created.projectWorkspaceId).toBe(sourceId); + } else await db.insert(issues).values(taskInput); + if (grants !== "none") await db.insert(principalPermissionGrants).values({ companyId, principalType: "agent", principalId: agentId, + permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }); + if (grants === "both") await db.insert(principalPermissionGrants).values({ companyId, principalType: "user", principalId: "responsible-user", + permissionKey: "tasks:assign_scope", scope: { projectId, assigneeAgentId: agentId } }); + const actor = { type: "agent" as const, agentId, companyId, source: "agent_jwt" as const, onBehalfOfUserId: "responsible-user" }; + expect(await accessService(db).decide({ actor, action: "project:read", resource: { type: "project", companyId, projectId } })).toMatchObject({ allowed: true }); + expect(await accessService(db).decide({ actor, action: "project:write_workspace", resource: { type: "issue", companyId, projectId, issueId, assigneeAgentId: agentId }, scope: { projectId, assigneeAgentId: agentId } })).toMatchObject({ allowed: grants === "both" }); + const environmentId = randomUUID(); + await db.insert(environments).values({ id: environmentId, name: `Standard sandbox ${companyId}`, driver: "sandbox", status: "active", config: { provider: "fake", image: "fake:test" } }); + await db.update(agents).set({ defaultEnvironmentId: placement === "sandbox" ? environmentId : null }).where(eq(agents.id, agentId)); + const driver = heartbeatService(db, { environmentRuntime: { ...environmentRuntimeService(db), execute: async input => ({ + exitCode: 0, stdout: execFileSync(input.command, input.args ?? [], { cwd: root, input: input.stdin, encoding: "utf8", env: { ...process.env, ...input.env } }), + stderr: "", signal: null, timedOut: false, + }) } }); + const run = await driver.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + expect(run).not.toBeNull(); + await vi.waitFor(async () => expect((await driver.getRun(run!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, driver); + expect(await driver.getRun(run!.id)).toMatchObject({ status: "succeeded", error: null }); + const [bound] = await db.select().from(issues).where(eq(issues.id, issueId)); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, bound.executionWorkspaceId!)); + expect(workspace.cwd).not.toBe(cwd); + expect(await readFile(path.join(workspace.cwd!, "source.txt"), "utf8")).toBe("read-only source bytes"); + const calls = execute.mock.calls.filter(([input]) => input.runId === run!.id); + expect(calls).toHaveLength(1); + expect(calls[0][0].context.paperclipEnvironment.driver).toBe(placement === "sandbox" ? "sandbox" : "local"); + if (!implicitDefault) await expect(readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + else expect(await readFile(marker, "utf8")).toBe("host-script-ran"); + await rm(marker, { force: true }); + if (grants === "none") await rm(workspace.cwd!, { recursive: true, force: true }); + await db.update(issues).set({ status: "todo" }).where(eq(issues.id, issueId)); + const { executionWorkspaceService } = await import("../services/execution-workspaces.js"); + if (placement === "local" && grants === "both") await db.update(issues).set({ projectId, workspaceSelection: null }).where(eq(issues.id, issueId)); + await executionWorkspaceService(db).update(workspace.id, { metadata: { ...workspace.metadata, + _controllerProvisionCopyOnly: false, config: { provisionCommand: `printf host-script-ran > '${marker}'` } } }); + const resumed = await driver.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", contextSnapshot: { issueId } }); + await vi.waitFor(async () => expect((await driver.getRun(resumed!.id))?.status).toMatch(/^(succeeded|failed)$/), { timeout: 15_000 }); + await drainHeartbeatRunsToQuiescence(db, driver); + expect(await driver.getRun(resumed!.id)).toMatchObject({ status: "succeeded", error: null }); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].executionWorkspaceId).toBe(workspace.id); + expect(await readFile(path.join(workspace.cwd!, "source.txt"), "utf8")).toBe("read-only source bytes"); + if (!implicitDefault) await expect(readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + else expect(await readFile(marker, "utf8")).toBe("host-script-ran"); + }, 30_000); + +}); diff --git a/server/src/__tests__/helpers/isolated-test-home.ts b/server/src/__tests__/helpers/isolated-test-home.ts new file mode 100644 index 00000000000..0463f1a6b6d --- /dev/null +++ b/server/src/__tests__/helpers/isolated-test-home.ts @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +export function createIsolatedTestHome() { + const { mkdtempSync, lstatSync, openSync, fchmodSync, closeSync, readdirSync, rmSync, constants } = fs; + const home = mkdtempSync(path.join(os.tmpdir(), "paperclip-vitest-home-")); + const identity = lstatSync(home); + const cleanup = () => { + let current; + try { current = lstatSync(home); } + catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") return; + throw error; + } + if (!current.isDirectory() || current.dev !== identity.dev || current.ino !== identity.ino) { + throw new Error("Refusing to clean a replaced isolated test home"); + } + const makeDirectoriesWritable = (directory: string) => { + const fd = openSync(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW); + try { fchmodSync(fd, 0o700); } + finally { closeSync(fd); } + for (const entry of readdirSync(directory, { withFileTypes: true })) { + if (entry.isDirectory()) makeDirectoriesWritable(path.join(directory, entry.name)); + } + }; + makeDirectoriesWritable(home); + rmSync(home, { recursive: true, force: true, maxRetries: 8, retryDelay: 50 }); + }; + return { home, cleanup }; +} diff --git a/server/src/__tests__/helpers/repository-trust-fixture.ts b/server/src/__tests__/helpers/repository-trust-fixture.ts new file mode 100644 index 00000000000..e766c2dd68c --- /dev/null +++ b/server/src/__tests__/helpers/repository-trust-fixture.ts @@ -0,0 +1,114 @@ +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { randomUUID } from "node:crypto"; +import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { vi, type Mock } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, authUsers, companies, companyMemberships, createDb, executionWorkspaces, heartbeatRuns, issueAccessGrants, issues } from "@paperclipai/db"; +import { executionWorkspaceRepositoryService } from "../../services/execution-workspace-repositories.js"; +import { startEmbeddedPostgresTestDatabase } from "./embedded-postgres.js"; + +export function createRepositoryTrustFixture(acquisition: { managedAuth: Mock; materialize: Mock; catalog: Mock }) { + let database: Awaited>; + let db: ReturnType; + let root: string; + async function setup() { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + database = await startEmbeddedPostgresTestDatabase("paperclip-repository-trust-"); + db = createDb(database.connectionString); + root = await mkdtemp(path.join(tmpdir(), "paperclip-repository-trust-")); + return { db, root }; + } + async function cleanup() { + vi.unstubAllEnvs(); + await database?.cleanup(); + if (root) await rm(root, { recursive: true, force: true }); + } + + async function fixture() { + const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), runId = randomUUID(); + const userId = randomUUID(), cwd = path.join(root, issueId); + await mkdir(cwd); + await db.insert(companies).values({ id: companyId, name: "Repository trust", issuePrefix: `R${companyId.slice(0, 6).toUpperCase()}` }); + await db.insert(authUsers).values({ id: userId, name: "Sponsor", email: `${userId}@example.test`, emailVerified: true, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(agents).values({ id: agentId, companyId, name: "Chat worker", adapterType: "process", status: "idle" }); + await db.insert(companyMemberships).values([ + { companyId, principalType: "user", principalId: userId, membershipRole: "operator", status: "active" }, + { companyId, principalType: "agent", principalId: agentId, membershipRole: "member", status: "active" }, + ]); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Task files", cwd, + mode: "isolated_workspace", strategyType: "task_directory" }).returning(); + await db.insert(issues).values({ id: issueId, companyId, title: "Unlinked chat work", assigneeAgentId: agentId, + executionWorkspaceId: workspace.id }); + await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, status: "running", responsibleUserId: userId, + contextSnapshot: { issueId } }); + await db.update(issues).set({ executionRunId: runId }).where(eq(issues.id, issueId)); + acquisition.catalog.mockResolvedValue({ repositories: [{ id: "private-repo", fullName: "sponsor/private", private: true, + url: "https://github.com/sponsor/private", connections: ["sponsor-github"] }] }); + acquisition.managedAuth.mockRejectedValue(new Error("Managed credentials must not be acquired")); + const actor = { type: "agent" as const, source: "agent_jwt" as const, companyId, agentId, runId, onBehalfOfUserId: userId }; + const policy = { trustPreset: "low_trust_review" as const, authorizationPolicy: { + trustPreset: "low_trust_review" as const, trustBoundary: { mode: "low_trust_review" as const, companyId, rootIssueId: issueId }, + } }; + const service = executionWorkspaceRepositoryService(db); + const request = (repository: { kind: "catalog"; id: string } | { kind: "url"; url: string }, requestKey = randomUUID()) => + service.request({ companyId, issueId, actor, request: { repository, requestKey } }); + const admission = { companyId, issueId, workspaceId: workspace.id, cwd, agentId, runId, responsibleUserId: userId }; + return { companyId, agentId, issueId, runId, userId, workspace, policy, service, actor, request, admission }; + } + + async function lowTrustPeer(f: Awaited>, cwd = f.admission.cwd) { + const [workspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Low-trust alias", + cwd, mode: "isolated_workspace", strategyType: "task_directory" }).returning(); + const issueId = randomUUID(), runId = randomUUID(); + await db.insert(issues).values({ id: issueId, companyId: f.companyId, title: "Low-trust peer", + assigneeAgentId: f.agentId, executionWorkspaceId: workspace.id }); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, rootIssueId: issueId } } }; + await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, status: "running", + responsibleUserId: f.userId, contextSnapshot: { issueId, executionPolicy: policy } }); + return { workspace, admission: { ...f.admission, cwd, issueId, runId, workspaceId: workspace.id }, actor: { ...f.actor, runId } }; + } + + async function standardPeer(f: Awaited>) { + const userId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), runId = randomUUID(); + await db.insert(authUsers).values({ id: userId, name: "Other sponsor", email: `${userId}@example.test`, emailVerified: true, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(agents).values({ id: agentId, companyId: f.companyId, name: "Other worker", adapterType: "process" }); + await db.insert(companyMemberships).values([ + { companyId: f.companyId, principalType: "user", principalId: userId, membershipRole: "member", status: "active" }, + { companyId: f.companyId, principalType: "agent", principalId: agentId, membershipRole: "member", status: "active" }, + ]); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Other task files", cwd: f.admission.cwd, + mode: "shared_workspace", strategyType: "operator_directory" }).returning(); + await db.insert(issues).values({ id: issueId, companyId: f.companyId, title: "Other task", assigneeAgentId: agentId, executionWorkspaceId: workspace.id }); + await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId, responsibleUserId: userId, status: "running", contextSnapshot: { issueId } }); + return { userId, agentId, issueId, runId, workspace, + actor: { ...f.actor, agentId, runId, onBehalfOfUserId: userId }, + admission: { ...f.admission, agentId, runId, issueId, workspaceId: workspace.id, responsibleUserId: userId } }; + } + async function privateTask(f: Awaited>) { + await db.update(issues).set({ visibility: "private", privacyRootIssueId: f.issueId, responsibleUserId: f.userId }).where(eq(issues.id, f.issueId)); + } + async function grantPeer(f: Awaited>, peer: Awaited>) { + return db.insert(issueAccessGrants).values([ + { issueId: f.issueId, subjectType: "agent", subjectId: peer.agentId, source: "explicit", grantedByUserId: f.userId }, + { issueId: f.issueId, subjectType: "user", subjectId: peer.userId, source: "explicit", grantedByUserId: f.userId }, + ]).returning(); + } + async function localPrivateClone(f: Awaited>, beforePublish?: () => Promise) { + const source = path.join(root, `${f.issueId}-privacy-source`); + await mkdir(source); + const exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", source]); + await writeFile(path.join(source, "private.txt"), "Private task edits\n"); + await exec("git", ["-C", source, "add", "."]); + await exec("git", ["-C", source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const actual = await vi.importActual("../../services/managed-repository-checkout.js"); + acquisition.materialize.mockImplementationOnce((cwd, input) => actual.materializeManagedProjectWorkspace(cwd, { ...input, localSource: source, + beforePublish: async cloneCwd => { await beforePublish?.(); await input.beforePublish(cloneCwd); } })); + } + + return { setup, cleanup, fixture, lowTrustPeer, standardPeer, privateTask, grantPeer, localPrivateClone }; +} diff --git a/server/src/__tests__/isolated-test-home.test.ts b/server/src/__tests__/isolated-test-home.test.ts new file mode 100644 index 00000000000..782de350f73 --- /dev/null +++ b/server/src/__tests__/isolated-test-home.test.ts @@ -0,0 +1,62 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { createRequire } from "node:module"; +import { afterEach, expect, it, vi } from "vitest"; +import { createIsolatedTestHome } from "./helpers/isolated-test-home.js"; + +afterEach(() => vi.restoreAllMocks()); + +it("removes immutable cache directories at process exit even after filesystem spies change", () => { + const helper = fileURLToPath(new URL("./helpers/isolated-test-home.ts", import.meta.url)); + const result = execFileSync(process.execPath, ["--import", createRequire(import.meta.url).resolve("tsx"), "--input-type=module", "-e", ` + import fs from 'node:fs'; + import path from 'node:path'; + const { createIsolatedTestHome } = await import(process.argv[1]); + const owned = createIsolatedTestHome(); + const cache = path.join(owned.home, 'runtime-skills', 'immutable'); + fs.mkdirSync(cache, { recursive: true }); + fs.writeFileSync(path.join(cache, 'SKILL.md'), 'read-only fixture'); + fs.chmodSync(path.join(cache, 'SKILL.md'), 0o444); + fs.chmodSync(cache, 0o555); + process.once('exit', owned.cleanup); + fs.rmSync = () => { throw Object.assign(new Error('simulated read-only filesystem'), { code: 'EACCES' }); }; + console.log(owned.home); + `, helper], { encoding: "utf8" }); + const home = result.trim(); + expect(path.basename(home)).toMatch(/^paperclip-vitest-home-/); + expect(fs.existsSync(home)).toBe(false); +}); + +it("never follows links from an owned home into another directory", () => { + const owned = createIsolatedTestHome(); + const outside = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cleanup-outside-")); + try { + fs.writeFileSync(path.join(outside, "private"), "retained"); + fs.chmodSync(outside, 0o555); + fs.symlinkSync(outside, path.join(owned.home, "external")); + owned.cleanup(); + expect(fs.existsSync(owned.home)).toBe(false); + expect(fs.readFileSync(path.join(outside, "private"), "utf8")).toBe("retained"); + expect(fs.statSync(outside).mode & 0o777).toBe(0o555); + } finally { + fs.chmodSync(outside, 0o700); + fs.rmSync(outside, { recursive: true, force: true }); + } +}); + +it("refuses a replaced home instead of deleting its replacement", () => { + const owned = createIsolatedTestHome(); + const retained = `${owned.home}-retained`; + fs.renameSync(owned.home, retained); + fs.symlinkSync(retained, owned.home); + try { + expect(owned.cleanup).toThrow("Refusing to clean a replaced isolated test home"); + expect(fs.statSync(retained).isDirectory()).toBe(true); + } finally { + fs.unlinkSync(owned.home); + fs.rmSync(retained, { recursive: true, force: true }); + } +}); diff --git a/server/src/__tests__/issue-activity-events-routes.test.ts b/server/src/__tests__/issue-activity-events-routes.test.ts index 38062852173..9f9a5f26a52 100644 --- a/server/src/__tests__/issue-activity-events-routes.test.ts +++ b/server/src/__tests__/issue-activity-events-routes.test.ts @@ -195,6 +195,7 @@ function issueUpdateWithReceipt(issue: ReturnType, patch: Reco actorRunId: _actorRunId, actorRunStopId: _actorRunStopId, actorUserId: _actorUserId, + workspaceSelectionActor: _workspaceSelectionActor, blockedByIssueIds: _blockedByIssueIds, ...issuePatch } = patch; diff --git a/server/src/__tests__/issue-closed-workspace-routes.test.ts b/server/src/__tests__/issue-closed-workspace-routes.test.ts index 4d4da5a6ceb..2ab80dadce7 100644 --- a/server/src/__tests__/issue-closed-workspace-routes.test.ts +++ b/server/src/__tests__/issue-closed-workspace-routes.test.ts @@ -270,6 +270,7 @@ describe("closed isolated workspace issue routes", () => { workspaceId: closedWorkspaceId, issue: { id: issueId, companyId: "company-1", projectId: null }, actor: expect.objectContaining({ actorType: "user" }), + authorizationActor: expect.objectContaining({ type: "board", source: "local_implicit" }), }); // The closed-workspace dead end is gone: the comment is accepted. expect(res.status).toBe(201); diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 6ccc594cd26..535990d7fd1 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -5458,7 +5458,7 @@ describeEmbeddedPostgres("issueService.create workspace inheritance", () => { }); }); - it("derives project identity when an update adds workspace linkage to a projectless issue", async () => { + it("preserves projectless organization when an update adds workspace linkage", async () => { const companyId = randomUUID(); const projectId = randomUUID(); const projectWorkspaceId = randomUUID(); @@ -5499,7 +5499,7 @@ describeEmbeddedPostgres("issueService.create workspace inheritance", () => { projectWorkspaceId, }); - expect(updated?.projectId).toBe(projectId); + expect(updated?.projectId).toBeNull(); expect(updated?.projectWorkspaceId).toBe(projectWorkspaceId); }); diff --git a/server/src/__tests__/legacy-finalization-regression.test.ts b/server/src/__tests__/legacy-finalization-regression.test.ts index f53692b9fbe..0babe10be88 100644 --- a/server/src/__tests__/legacy-finalization-regression.test.ts +++ b/server/src/__tests__/legacy-finalization-regression.test.ts @@ -4,7 +4,9 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { eq } from "drizzle-orm"; import { agents, + authUsers, companies, + companyMemberships, completionContracts, createDb, heartbeatRuns, @@ -67,6 +69,13 @@ describe("P6-32 legacy finalization regression", () => { status: "active", defaultResponsibleUserId: "responsible-user", }); + await db.insert(authUsers).values({ + id: "responsible-user", name: "Responsible user", email: "legacy-finalization@example.test", + createdAt: new Date(), updatedAt: new Date(), + }); + await db.insert(companyMemberships).values({ + companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member", + }); await db.insert(projects).values({ id: projectId, companyId, name: "Legacy project", status: "active" }); await db.insert(projectWorkspaces).values({ id: projectWorkspaceId, diff --git a/server/src/__tests__/managed-repository-checkout.test.ts b/server/src/__tests__/managed-repository-checkout.test.ts new file mode 100644 index 00000000000..26c10afa7b2 --- /dev/null +++ b/server/src/__tests__/managed-repository-checkout.test.ts @@ -0,0 +1,71 @@ +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { mkdtemp, mkdir, writeFile, readFile, readdir, rm, stat } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { materializeManagedProjectWorkspace, ensureManagedRepositoriesIgnored } from "../services/managed-repository-checkout.js"; + +const execFile = promisify(execFileCallback); +const directories: string[] = []; +afterEach(async () => { await Promise.all(directories.splice(0).map(dir => rm(dir, { recursive: true, force: true }))); }); +async function fixture() { + const root = await mkdtemp(path.join(tmpdir(), "paperclip-repository-publication-")); + directories.push(root); + const source = path.join(root, "source"); + await mkdir(source); + await execFile("git", ["init", "--initial-branch=main", source]); + await writeFile(path.join(source, "source.txt"), "initial\n"); + await execFile("git", ["-C", source, "add", "."]); + await execFile("git", ["-C", source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "initial"]); + return { root, source, target: path.join(root, "target") }; +} +describe("atomic managed repository publication", () => { + it("keeps managed repositories separate without requiring Git or touching an ancestor", async () => { + const { source, target } = await fixture(); + await mkdir(target); + await expect(ensureManagedRepositoriesIgnored(target)).resolves.toBeUndefined(); + const nestedPlainRoot = path.join(source, "plain-task"); + await mkdir(nestedPlainRoot); + const original = await readFile(path.join(source, ".git", "info", "exclude"), "utf8"); + await ensureManagedRepositoriesIgnored(nestedPlainRoot); + expect(await readFile(path.join(source, ".git", "info", "exclude"), "utf8")).toBe(original); + await ensureManagedRepositoriesIgnored(source); + expect(await readFile(path.join(source, ".git", "info", "exclude"), "utf8")).toContain("/.paperclip-repositories/"); + }); + it("writes the pinned receipt inside the clone before making it visible", async () => { + const { source, target } = await fixture(); + const result = await materializeManagedProjectWorkspace(target, { repoUrl: "https://github.com/test/source", localSource: source, beforePublish: async clone => { + expect(await stat(target).catch(() => null)).toBeNull(); + const commit = (await execFile("git", ["-C", clone, "rev-parse", "HEAD"])).stdout.trim(); + await writeFile(path.join(clone, ".git", "paperclip-workspace-owner.json"), JSON.stringify({ repositoryId: "receipt", pinnedCommit: commit })); + } }); + expect(result.warning).toBeNull(); + expect(JSON.parse(await readFile(path.join(target, ".git", "paperclip-workspace-owner.json"), "utf8"))).toMatchObject({ repositoryId: "receipt", pinnedCommit: expect.stringMatching(/^[a-f0-9]{40}$/) }); + expect(await readFile(path.join(target, "source.txt"), "utf8")).toBe("initial\n"); + }); + it("authorizes the empty staging root before copying any source bytes", async () => { + const { root, source, target } = await fixture(); + let stagingChecked = false; + await expect(materializeManagedProjectWorkspace(target, { + repoUrl: source, + beforeCopy: async staging => { + expect(await readdir(staging)).toEqual([]); + expect(await stat(target).catch(() => null)).toBeNull(); + stagingChecked = true; + throw new Error("Private staging source denied"); + }, + })).rejects.toThrow("Private staging source denied"); + expect(stagingChecked).toBe(true); + expect(await stat(target).catch(() => null)).toBeNull(); + expect((await readdir(root)).filter(name => name.startsWith("target.clone-"))).toEqual([]); + expect(await readFile(path.join(source, "source.txt"), "utf8")).toBe("initial\n"); + }); + it("does not publish an incomplete checkout when its receipt cannot be committed", async () => { + const { root, source, target } = await fixture(); + await expect(materializeManagedProjectWorkspace(target, { repoUrl: "https://github.com/test/source", localSource: source, beforePublish: async () => { throw new Error("receipt failed"); } })).rejects.toThrow(/receipt failed/); + expect(await stat(target).catch(() => null)).toBeNull(); + expect((await readdir(root)).filter(name => name.startsWith("target.clone-"))).toEqual([]); + expect(await readFile(path.join(source, "source.txt"), "utf8")).toBe("initial\n"); + }); +}); diff --git a/server/src/__tests__/native-session-resumption.test.ts b/server/src/__tests__/native-session-resumption.test.ts index 1283b29a463..f772c70b4f2 100644 --- a/server/src/__tests__/native-session-resumption.test.ts +++ b/server/src/__tests__/native-session-resumption.test.ts @@ -1,3 +1,4 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import { once } from "node:events"; @@ -9,6 +10,8 @@ import { agentWakeupRequests, agents, companies, + authUsers, + companyMemberships, completionContracts, createDb, executionWorkspaces, @@ -260,7 +263,7 @@ describe("P6-25 pre-result native session recovery", () => { leaseExpiresAt: new Date(0), })), ]); - }, 30_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterAll(async () => temporary?.cleanup()); @@ -795,6 +798,8 @@ describe.each(["unchanged", "newer_active", "stale_idle"] as const)( status: "active", defaultResponsibleUserId: "responsible-user", }); + await db.insert(authUsers).values({ id: "responsible-user", name: "Fixture user", email: "responsible-user@example.test", createdAt: new Date(), updatedAt: new Date() }).onConflictDoNothing(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: "responsible-user", status: "active", membershipRole: "member" }); await db .insert(projects) .values({ @@ -952,7 +957,7 @@ describe.each(["unchanged", "newer_active", "stale_idle"] as const)( authorUserId: "responsible-user", body: "Verify the newest user instruction before completing this task.", }); - }, 30_000); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); afterAll(async () => { if ( diff --git a/server/src/__tests__/native-workspace-sync-history.test.ts b/server/src/__tests__/native-workspace-sync-history.test.ts index d8c2e46f8a5..56b4a66c2f2 100644 --- a/server/src/__tests__/native-workspace-sync-history.test.ts +++ b/server/src/__tests__/native-workspace-sync-history.test.ts @@ -8,7 +8,7 @@ import type { EnvironmentLease } from "@paperclipai/shared"; import type { AdapterSandboxExecutionTarget } from "@paperclipai/adapter-utils/execution-target"; import type { CommandManagedRuntimeRunner } from "@paperclipai/adapter-utils/command-managed-runtime"; import { runLocalGit } from "@paperclipai/adapter-utils/git-workspace-sync"; -import { prepareNativeWorkspaceSync } from "../services/native-runtime/native-workspace-sync.js"; +import { prepareNativeWorkspaceSync, nativeWorkspaceSyncInternals } from "../services/native-runtime/native-workspace-sync.js"; const runner: CommandManagedRuntimeRunner = { execute: (input) => new Promise((resolve, reject) => { @@ -44,7 +44,7 @@ describe("native warm workspace Git history", () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); }); - it.each(["unchanged", "host_commit", "host_branch", "remote_commit", "nested_commit"] as const)( + it.each(["unchanged", "host_commit", "host_branch", "host_origin", "remote_commit", "nested_commit", "plain_nested_commit"] as const)( "checks Git identity before reusing a warm sandbox: %s", async (change) => { const root = await mkdtemp(path.join(os.tmpdir(), "paperclip-warm-history-")); roots.push(root); @@ -61,9 +61,10 @@ describe("native warm workspace Git history", () => { await git(dir, "add", "."); await git(dir, "commit", "-m", "base"); } - await initRepo(host); + if (change === "plain_nested_commit") await mkdir(host, { recursive: true }); + else await initRepo(host); const nestedPath = path.join(".paperclip-repositories", "nested"); - if (change === "nested_commit") await initRepo(path.join(host, nestedPath)); + if (change === "nested_commit" || change === "plain_nested_commit") await initRepo(path.join(host, nestedPath)); const lease = { id: "lease", providerLeaseId: "sandbox", metadata: {} } as EnvironmentLease; let runnerProfileJson: Record = {}; // Persist the real service's descriptors and stamps; only the DB query @@ -94,29 +95,40 @@ describe("native warm workspace Git history", () => { const input = { db: db as unknown as Db, companyId: "company", workspaceId: "workspace", workspaceLocalDir: host, lease, target }; const first = (await prepareNativeWorkspaceSync({ ...input, runId: "first" }))!; expect(first.mode).toBe("host_current"); + expect(first.reference.schema).toBe("paperclip.native-workspace-sync/v3"); + if (change === "plain_nested_commit") { + const recovered = await nativeWorkspaceSyncInternals.readDescriptor({ runId: "first", reference: first.reference }); + expect(recovered.descriptor.gitSnapshot).toBeNull(); + expect(recovered.descriptor.repositories?.[0]?.path).toBe(nestedPath); + } await first.restoreWorkspace(); await first.cleanup(); - if (change === "host_commit" || change === "nested_commit") { - await git(change === "nested_commit" ? path.join(host, nestedPath) : host, "commit", "--allow-empty", "-m", "host only"); + if (change === "host_commit" || change === "nested_commit" || change === "plain_nested_commit") { + await git(change === "nested_commit" || change === "plain_nested_commit" ? path.join(host, nestedPath) : host, "commit", "--allow-empty", "-m", "host only"); } else if (change === "host_branch") { await git(host, "checkout", "-b", "other"); + } else if (change === "host_origin") { + await git(host, "remote", "add", "origin", "https://github.com/paperclipai/new-source.git"); } else if (change === "remote_commit") { await git(remote, "-c", "user.name=Test", "-c", "user.email=test@paperclip.dev", "commit", "--allow-empty", "-m", "remote only"); } - const expectedHead = await git(host, "rev-parse", "HEAD"); - const expectedBranch = await git(host, "symbolic-ref", "--short", "HEAD"); - const nestedHead = change === "nested_commit" ? await git(path.join(host, nestedPath), "rev-parse", "HEAD") : null; + const hostRepo = change === "plain_nested_commit" ? path.join(host, nestedPath) : host; + const remoteRepo = change === "plain_nested_commit" ? path.join(remote, nestedPath) : remote; + const expectedHead = await git(hostRepo, "rev-parse", "HEAD"); + const expectedBranch = await git(hostRepo, "symbolic-ref", "--short", "HEAD"); + const nestedHead = change === "nested_commit" || change === "plain_nested_commit" ? await git(path.join(host, nestedPath), "rev-parse", "HEAD") : null; runnerProfileJson = {}; const second = (await prepareNativeWorkspaceSync({ ...input, runId: "second", target: { ...target, sandboxLeaseAcquisition: { outcome: "resumed", providerLeaseId: "sandbox" }, } }))!; try { expect(second.mode).toBe(change === "unchanged" ? "adopt_remote" : "host_current"); - expect(await git(remote, "rev-parse", "HEAD")).toBe(expectedHead); - expect(await git(remote, "symbolic-ref", "--short", "HEAD")).toBe(expectedBranch); + expect(await git(remoteRepo, "rev-parse", "HEAD")).toBe(expectedHead); + expect(await git(remoteRepo, "symbolic-ref", "--short", "HEAD")).toBe(expectedBranch); + if (change === "host_origin") expect(await git(remote, "remote", "get-url", "origin")).toBe("https://github.com/paperclipai/new-source.git"); await second.restoreWorkspace(); - expect(await git(host, "rev-parse", "HEAD")).toBe(expectedHead); + expect(await git(hostRepo, "rev-parse", "HEAD")).toBe(expectedHead); if (nestedHead) { expect(await git(path.join(remote, nestedPath), "rev-parse", "HEAD")).toBe(nestedHead); expect(await git(path.join(host, nestedPath), "rev-parse", "HEAD")).toBe(nestedHead); diff --git a/server/src/__tests__/native-workspace-sync.test.ts b/server/src/__tests__/native-workspace-sync.test.ts index 072e8fdcedd..f690abcd5be 100644 --- a/server/src/__tests__/native-workspace-sync.test.ts +++ b/server/src/__tests__/native-workspace-sync.test.ts @@ -13,6 +13,7 @@ import { classifyNativeWorkspaceInbound, nativeWorkspaceSyncInternals, readNativeWorkspaceSyncReference, + readNativeWorkspaceSyncLocalCwd, resumeNativeWorkspaceSync, prepareNativeWorkspaceSync, cleanupNativeWorkspaceSync, @@ -185,6 +186,9 @@ describe("native workspace sync durable metadata", () => { const recovered = await nativeWorkspaceSyncInternals.readDescriptor({ runId, reference }); expect(recovered.baseline.entries.get("private-filename-雪")).toMatchObject({ kind: "file" }); await disposeDirectorySnapshot(recovered.baseline); + await expect(readNativeWorkspaceSyncLocalCwd({ runId, companyId: "company", workspaceId: "workspace", reference })).resolves.toBe(workspace); + await expect(readNativeWorkspaceSyncLocalCwd({ runId, companyId: "other-company", workspaceId: "workspace", reference })).rejects.toThrow(/binding_mismatch/); + await expect(readNativeWorkspaceSyncLocalCwd({ runId, companyId: "company", workspaceId: "other-workspace", reference })).rejects.toThrow(/binding_mismatch/); if (tamper === "corrupt") { await appendFile(serialized.entries.filePath, "corrupt"); } else if (tamper === "symlink") { diff --git a/server/src/__tests__/openapi-routes.test.ts b/server/src/__tests__/openapi-routes.test.ts index 72489b96fda..a4093e52e25 100644 --- a/server/src/__tests__/openapi-routes.test.ts +++ b/server/src/__tests__/openapi-routes.test.ts @@ -50,6 +50,7 @@ const apiPrefixes: Record = { "decision-training.ts": "/api", "environments.ts": "/api", "execution-workspaces.ts": "/api", + "task-workspaces.ts": "/api", "file-resources.ts": "/api", "folders.ts": "/api", "goals.ts": "/api", diff --git a/server/src/__tests__/pipelines-routes.test.ts b/server/src/__tests__/pipelines-routes.test.ts index 3cb7bb7243d..4e5a7e36a15 100644 --- a/server/src/__tests__/pipelines-routes.test.ts +++ b/server/src/__tests__/pipelines-routes.test.ts @@ -598,7 +598,7 @@ describeEmbeddedPostgres("pipeline routes", () => { }); }); - it("fails automation execution when the selected project workspace belongs to a different project", async () => { + it("keeps automation project organization independent of the selected workspace source", async () => { const company = await seedCompany(); const http = request(app(boardActor)); const agent = await seedAutomationAgent(company.id); @@ -621,7 +621,7 @@ describeEmbeddedPostgres("pipeline routes", () => { config: { automation: { assigneeAgentId: agent.id, - instructionsBody: "This should fail before issue creation.", + instructionsBody: "Work in the selected source while retaining the task's project.", projectId: source.projectId, projectWorkspaceId: mismatched.projectWorkspaceId, }, @@ -638,14 +638,21 @@ describeEmbeddedPostgres("pipeline routes", () => { .send({ toStageKey: "in_progress", expectedVersion: 1 }) .expect(200); - expect(moved.body.automationExecution.status).toBe("failed"); + expect(moved.body.automationExecution.status).toBe("succeeded"); const executionId = moved.body.automationExecution.execution.id as string; const [execution] = await db .select() .from(pipelineAutomationExecutions) .where(eq(pipelineAutomationExecutions.id, executionId)); - expect(execution!.executionIssueId).toBeNull(); - expect(execution!.error).toContain("Project workspace must belong to the selected project"); + expect(execution!.executionIssueId).not.toBeNull(); + expect(execution!.error).toBeNull(); + const [issue] = await db.select().from(issues) + .where(eq(issues.id, execution!.executionIssueId!)); + expect(issue).toMatchObject({ + companyId: company.id, + projectId: source.projectId, + projectWorkspaceId: mismatched.projectWorkspaceId, + }); }); it("keeps legacy stage automation with only assignee and instructions compatible", async () => { diff --git a/server/src/__tests__/project-workspace-root-provenance.test.ts b/server/src/__tests__/project-workspace-root-provenance.test.ts new file mode 100644 index 00000000000..0bc5ba24329 --- /dev/null +++ b/server/src/__tests__/project-workspace-root-provenance.test.ts @@ -0,0 +1,91 @@ +import { randomUUID } from "node:crypto"; +import { mkdtemp, mkdir, rm, writeFile, realpath } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { pathToFileURL } from "node:url"; +import path from "node:path"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, authUsers, companies, companyMemberships, createDb, executionWorkspaceRepositoryAccess, + executionWorkspaces, issues, projects, projectWorkspaces, projectAccessMembers, principalPermissionGrants, + EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { projectService } from "../services/projects.js"; +import { assertExecutionWorkspaceRootReadAccess, assertExecutionWorkspaceRootWriteAccess } from "../services/execution-workspace-repositories.js"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("configured project root provenance", () => { + let database: Awaited>; + let db: ReturnType; + let root: string; + beforeAll(async () => { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + database = await startEmbeddedPostgresTestDatabase("paperclip-configured-provenance-"); + db = createDb(database.connectionString); + root = await mkdtemp(path.join(tmpdir(), "paperclip-configured-provenance-")); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterAll(async () => { + vi.unstubAllEnvs(); + await database?.cleanup(); + if (root) await rm(root, { recursive: true, force: true }); + }); + + it.each((["cwd", "file_url", "absolute_path"] as const).flatMap(sourceKind => + (["move", "remove", "remove_project", "replace_repositories"] as const).map(mutation => ({ sourceKind, mutation }))))( + "retains nested private files before first admission ($sourceKind / $mutation)", async ({ sourceKind, mutation }) => { + const companyId = randomUUID(), userId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(); + const cwd = path.join(root, companyId), privateCwd = path.join(cwd, "private"), unrelatedCwd = `${cwd}-unrelated`; + await mkdir(privateCwd, { recursive: true }); + await mkdir(unrelatedCwd); + await writeFile(path.join(privateCwd, "secret.txt"), "Private configured source\n"); + await db.insert(companies).values({ id: companyId, name: "Configured provenance", issuePrefix: `P${companyId.slice(0, 6)}` }); + await db.insert(authUsers).values({ id: userId, name: "Reader", email: `${userId}@example.test`, emailVerified: true, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(agents).values({ id: agentId, companyId, name: "Worker", adapterType: "process", status: "idle" }); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, membershipRole: "member", status: "active" }); + const [source] = await db.insert(projects).values({ companyId, name: "Private source", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const sourceUrl = (cwd: string) => sourceKind === "file_url" ? pathToFileURL(cwd).href : cwd; + const [configured] = await db.insert(projectWorkspaces).values({ companyId, projectId: source.id, name: "Private files", + cwd: sourceKind === "cwd" ? privateCwd : null, + repoUrl: sourceKind === "cwd" ? "https://github.com/example/private" : sourceUrl(privateCwd), + metadata: { githubRepositoryId: "fixture-private" } }).returning(); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Public parent", cwd, mode: "shared_workspace", strategyType: "task_directory" }).returning(); + await db.insert(issues).values({ id: issueId, companyId, title: "Public task", assigneeAgentId: agentId, executionWorkspaceId: workspace.id }); + const actor = { type: "board" as const, source: "session" as const, userId, ignoreInstanceAdmin: true }; + const access = { companyId, cwd, sourceWorkspaceId: workspace.id, actor }; + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, companyId))).toEqual([]); + const service = projectService(db, { captureWorkspaceUpdates: false }); + if (mutation === "move") await service.updateWorkspace(source.id, configured.id, + sourceKind === "cwd" ? { cwd: unrelatedCwd } : { repoUrl: sourceUrl(unrelatedCwd) }); + else if (mutation === "remove") await service.removeWorkspace(source.id, configured.id); + else if (mutation === "remove_project") await service.remove(source.id); + else await service.replaceRepositories(source.id, []); + const retained = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, companyId)); + expect(retained).toEqual(expect.arrayContaining([ + expect.objectContaining({ kind: "project_source", scopeId: source.id, canonicalCwd: await realpath(privateCwd) }), + expect.objectContaining({ kind: "shared_project_source", scopeId: source.id, canonicalCwd: await realpath(privateCwd) }), + ])); + expect(retained.some(row => row.kind === "reader")).toBe(false); + await expect(assertExecutionWorkspaceRootReadAccess(db, access)).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { ...access, issueId })).rejects.toThrow(/source project/); + if (mutation !== "move") await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId, cwd: unrelatedCwd, actor })).resolves.toBeUndefined(); + if (mutation === "remove_project") return; + const [readGrant] = await db.insert(projectAccessMembers).values({ companyId, projectId: source.id, subjectType: "user", subjectId: userId }).returning(); + await expect(assertExecutionWorkspaceRootReadAccess(db, access)).resolves.toBeUndefined(); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { ...access, issueId })).rejects.toThrow(/protected|not authorized/); + await db.insert(principalPermissionGrants).values({ companyId, principalType: "user", principalId: userId, + permissionKey: "tasks:assign_scope", scope: { projectId: source.id, assigneeAgentId: agentId } }); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { ...access, issueId })).resolves.toBeUndefined(); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.id, readGrant.id)); + await expect(assertExecutionWorkspaceRootReadAccess(db, access)).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { ...access, issueId })).rejects.toThrow(/source project/); + }); + + it("can remove a configured folder that never existed without inventing provenance", async () => { + const companyId = randomUUID(); + await db.insert(companies).values({ id: companyId, name: "Missing source", issuePrefix: `M${companyId.slice(0, 6)}` }); + const [source] = await db.insert(projects).values({ companyId, name: "Missing private source", visibility: "private" }).returning(); + const [configured] = await db.insert(projectWorkspaces).values({ companyId, projectId: source.id, name: "Missing", cwd: path.join(root, companyId, "never-created") }).returning(); + await expect(projectService(db, { captureWorkspaceUpdates: false }).removeWorkspace(source.id, configured.id)).resolves.toMatchObject({ id: configured.id }); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, companyId))).toEqual([]); + }); +}); diff --git a/server/src/__tests__/remove-test-home.test.ts b/server/src/__tests__/remove-test-home.test.ts index 4eb8cc182d2..724f141f7a6 100644 --- a/server/src/__tests__/remove-test-home.test.ts +++ b/server/src/__tests__/remove-test-home.test.ts @@ -49,7 +49,7 @@ describe("removeTestHome", () => { // The plain removal the setup file used to run is what fails in CI. if (process.platform !== "win32" && process.getuid?.() !== 0) { - expect(() => fs.rmSync(home, { recursive: true, force: true })).toThrow(/EACCES|EPERM/); + expect(() => fs.rmSync(home, { recursive: true, force: true })).toThrow(/EACCES|EPERM|ENOTEMPTY/); expect(fs.existsSync(bundle)).toBe(true); } diff --git a/server/src/__tests__/remove-test-home.ts b/server/src/__tests__/remove-test-home.ts index 524b1e34f67..760c3cc9dad 100644 --- a/server/src/__tests__/remove-test-home.ts +++ b/server/src/__tests__/remove-test-home.ts @@ -19,15 +19,15 @@ export function removeTestHome(home: string): void { try { fs.rmSync(home, options); } catch (error) { - if (!isPermissionError(error)) throw error; + if (!isReadOnlyDirectoryRemovalError(error)) throw error; makeDirectoriesWritable(home); fs.rmSync(home, options); } } -function isPermissionError(error: unknown): boolean { +function isReadOnlyDirectoryRemovalError(error: unknown): boolean { const code = (error as NodeJS.ErrnoException | null)?.code; - return code === "EACCES" || code === "EPERM"; + return code === "EACCES" || code === "EPERM" || code === "ENOTEMPTY"; } function makeDirectoriesWritable(directory: string): void { diff --git a/server/src/__tests__/run-trust-preset.test.ts b/server/src/__tests__/run-trust-preset.test.ts index d323f0f2b78..a5d72d70877 100644 --- a/server/src/__tests__/run-trust-preset.test.ts +++ b/server/src/__tests__/run-trust-preset.test.ts @@ -62,7 +62,10 @@ it("dispatch retains raw trust before workspace and broker setup", () => { const dispatch = heartbeat.slice(start, end); expect(start).toBeGreaterThan(0); expect(dispatch).toContain( - "executionWorkspacePolicy: projectContext.executionWorkspacePolicy", + "executionWorkspacePolicy: taskProjectContext.executionWorkspacePolicy", + ); + expect(dispatch).toContain( + "executionWorkspacePolicy: workspaceProjectContext.executionWorkspacePolicy", ); expect(dispatch).toContain( "context.executionPolicy = retainedTrust.executionPolicy", diff --git a/server/src/__tests__/setup-supertest.ts b/server/src/__tests__/setup-supertest.ts index 90a8d75a3c4..82472092bf3 100644 --- a/server/src/__tests__/setup-supertest.ts +++ b/server/src/__tests__/setup-supertest.ts @@ -1,11 +1,11 @@ import fs from "node:fs"; +import { createIsolatedTestHome } from "./helpers/isolated-test-home.js"; import { createRequire } from "node:module"; import type { AddressInfo, Server as NetServer } from "node:net"; import os from "node:os"; import path from "node:path"; import { Server as TlsServer } from "node:tls"; -import { removeTestHome } from "./remove-test-home.js"; type SupertestServer = NetServer & { address(): ReturnType; @@ -27,13 +27,11 @@ type SupertestTestConstructor = { process.env.PAPERCLIP_TEST_HOST_HOME ??= process.env.PAPERCLIP_HOME?.trim() || path.join(os.homedir(), ".paperclip"); // Receipt-spool recovery must never scan a developer's instance during tests. -const paperclipTestHome = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-vitest-home-")); +const { home: paperclipTestHome, cleanup: cleanupPaperclipTestHome } = createIsolatedTestHome(); process.env.PAPERCLIP_HOME = paperclipTestHome; // Setup-file afterAll hooks run before suites drain asynchronous heartbeats. // Cleanup at worker exit, after all suite teardown, avoids deleting live state. -// A run still finishing at exit can leave a read-only runtime-context bundle -// under this home, which a plain recursive rm cannot remove. -process.once("exit", () => removeTestHome(paperclipTestHome)); +process.once("exit", cleanupPaperclipTestHome); const require = createRequire(import.meta.url); const SupertestTest = require("supertest/lib/test.js") as SupertestTestConstructor; diff --git a/server/src/__tests__/task-workspace-admission.test.ts b/server/src/__tests__/task-workspace-admission.test.ts new file mode 100644 index 00000000000..cb8c3bd14e8 --- /dev/null +++ b/server/src/__tests__/task-workspace-admission.test.ts @@ -0,0 +1,37 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { ensurePersistedExecutionWorkspaceAvailable } from "../services/workspace-runtime.js"; +import { describe, expect, it } from "vitest"; +import { canApplyTaskWorkspaceSelectionAtAdmission, taskWorkspaceRuntimeSelectionEnabled, parseIssueExecutionWorkspaceSettings, resolveExecutionWorkspaceMode } from "../services/execution-workspace-policy.js"; + +describe("task workspace admission authority", () => { + it.each([{ hasTypedSelection: true, hasBinding: false }, { hasTypedSelection: false, hasBinding: true }])("keeps isolated intent authoritative with the legacy UI disabled: %j", flags => { + const enabled = taskWorkspaceRuntimeSelectionEnabled({ legacyUiEnabled: false, ...flags }); + const settings = enabled ? parseIssueExecutionWorkspaceSettings({ mode: "isolated_workspace" }) : null; + expect(resolveExecutionWorkspaceMode({ projectPolicy: null, issueSettings: settings, legacyUseProjectWorkspace: null })).toBe("isolated_workspace"); + }); + it.each([ + { admittedInput: { schema: "paperclip.native-execution-input.v3", binding: { executionWorkspaceId: "old-projectless-run" } }, restarting: false, hasLeaseOwner: false }, + { admittedInput: null, restarting: true, hasLeaseOwner: false }, + { admittedInput: null, restarting: false, hasLeaseOwner: true }, + ])("defers pending root changes while immutable execution/recovery owns the root", admission => { + expect(canApplyTaskWorkspaceSelectionAtAdmission(admission)).toBe(false); + }); + it("keeps a persisted task-owned source projectless when current organizational defaults have a project", async () => { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-source-authority-")); + try { + const restored = await ensurePersistedExecutionWorkspaceAvailable({ + base: { baseCwd: cwd, source: "project_primary", projectId: "org-project", workspaceId: "org-source", repoUrl: null, repoRef: null }, + workspace: { mode: "shared_workspace", strategyType: "project_primary", cwd, providerRef: cwd, + projectId: null, projectWorkspaceId: null, repoUrl: null, baseRef: null, branchName: null }, + issue: { id: "task", identifier: null, title: "Retain files" }, + agent: { id: "agent", companyId: "company", name: "Agent" }, + }); + expect(restored).toMatchObject({ cwd, projectId: null, workspaceId: null }); + } finally { await fs.rm(cwd, { recursive: true, force: true }); } + }); + it("permits new admission to apply explicit pending intent", () => { + expect(canApplyTaskWorkspaceSelectionAtAdmission({ admittedInput: null, restarting: false, hasLeaseOwner: false })).toBe(true); + }); +}); diff --git a/server/src/__tests__/task-workspace-api.test.ts b/server/src/__tests__/task-workspace-api.test.ts new file mode 100644 index 00000000000..393702a3354 --- /dev/null +++ b/server/src/__tests__/task-workspace-api.test.ts @@ -0,0 +1,167 @@ +import { randomUUID } from "node:crypto"; +import { mkdtemp } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, executionWorkspaceRepositories, executionWorkspaces, issues, heartbeatRuns, projects, projectWorkspaces, principalPermissionGrants, companyMemberships } from "@paperclipai/db"; +import { createLocalAgentJwt } from "../agent-auth-jwt.js"; +import { startRunnerApiTestServer } from "./helpers/runner-api-server.js"; +import { getEmbeddedPostgresTestSupport } from "./helpers/embedded-postgres.js"; +import { PaperclipRunnerToolAuthority } from "../services/native-runtime/paperclip-runner-tool-authority.js"; + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("task workspace API and native semantic authority", () => { + let server: Awaited>; + const originalSecret = process.env.PAPERCLIP_AGENT_JWT_SECRET; + beforeAll(async () => { process.env.PAPERCLIP_AGENT_JWT_SECRET = randomUUID(); server = await startRunnerApiTestServer(); }, 60_000); + afterAll(async () => { await server?.close(); if (originalSecret === undefined) delete process.env.PAPERCLIP_AGENT_JWT_SECRET; else process.env.PAPERCLIP_AGENT_JWT_SECRET = originalSecret; }); + it("creates an explicit-workspace child through the authenticated low-trust API without repeating its saved policy", async () => { + const f = await server.fixture({ connectionScenario: "fresh", disableWakeOnDemand: true }); + const policy = { trustPreset: "low_trust_review", authorizationPolicy: { trustPreset: "low_trust_review", + trustBoundary: { mode: "low_trust_review", companyId: f.companyId, rootIssueId: f.issueId, projectIds: [f.projectId] } } }; + const [workspace] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: f.projectId, + projectWorkspaceId: f.projectWorkspaceId, sourceIssueId: f.issueId, name: "Review files", cwd: f.workspace, + mode: "isolated_workspace", strategyType: "task_directory" }).returning(); + await server.db.update(projects).set({ executionWorkspacePolicy: policy }).where(eq(projects.id, f.projectId)); + await server.db.update(issues).set({ executionPolicy: policy, executionWorkspaceId: workspace.id }).where(eq(issues.id, f.issueId)); + await server.db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }).where(eq(heartbeatRuns.id, f.runId)); + const token = createLocalAgentJwt(f.agentId, f.companyId, "paperclip_runner", f.runId, f.userId); + const create = (parentId: string) => fetch(`${server.apiUrl}/api/companies/${f.companyId}/issues`, { + method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify({ title: `Review child ${randomUUID()}`, parentId, projectId: f.projectId, + assigneeAgentId: f.agentId, status: "backlog", workspaceSelection: { kind: "existing", workspaceId: workspace.id } }), + }); + const response = await create(f.issueId), body = await response.json(); + expect({ status: response.status, body }).toMatchObject({ status: 201, body: { + parentId: f.issueId, executionWorkspaceId: workspace.id, + executionPolicy: { authorizationPolicy: { trustBoundary: policy.authorizationPolicy.trustBoundary } }, + } }); + const denied = await create(f.blockerId); + expect(denied.status).toBe(403); + }); + it("inspects an idle admitted workspace, requests a repository, and stages root selection without interrupting", async () => { + const f = await server.fixture({ conversation: true, disableWakeOnDemand: true }); + const [workspace] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: null, sourceIssueId: f.issueId, name: "Task files", cwd: f.workspace, mode: "shared_workspace", strategyType: "task_directory", status: "idle" }).returning(); + await server.db.update(issues).set({ projectId: null, projectWorkspaceId: null, executionWorkspaceId: workspace.id }).where(eq(issues.id, f.issueId)); + const authority = new PaperclipRunnerToolAuthority(server.db, { companyId: f.companyId, issueId: f.issueId, agentId: f.agentId, runId: f.runId, apiUrl: server.apiUrl, workspaceRoot: "/admitted/task" }); + const call = (tool: string, args: Record) => authority.execute({ tool, arguments: args, callId: randomUUID() }); + const view = await call("get_workspace", {}) as { bindingRevision: number; cwd: string; workspace: { id: string } }; + expect(view).toMatchObject({ cwd: "/admitted/task", workspace: { id: workspace.id } }); + const args = { repository: { kind: "url", url: "https://github.com/public/example" }, requestKey: "repository-one" }; + const first = await call("prepare_repository", args) as { preparationId: string; kind: string }; + expect(first.kind).toBe("requires_next_admission"); + expect(await call("prepare_repository", args)).toMatchObject({ preparationId: first.preparationId }); + expect(await call("select_workspace", { selection: { kind: "task_directory" }, expectedBindingRevision: view.bindingRevision, requestKey: "root-one" })).toMatchObject({ kind: "scheduled", applies: "next_normal_admission" }); + const [task] = await server.db.select().from(issues).where(eq(issues.id, f.issueId)); + expect(task).toMatchObject({ executionWorkspaceId: workspace.id, projectId: null, workspacePendingSelection: { requestKey: "root-one" } }); + const runs = await server.db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, f.companyId)); + expect(runs).toHaveLength(1); + expect(runs[0]).toMatchObject({ id: f.runId, status: "running" }); + }); + it("keeps projectless protected-folder aliases readable while enforcing scoped write grants", async () => { + const f = await server.fixture({ conversation: true, disableWakeOnDemand: true }); + const [project] = await server.db.insert(projects).values({ companyId: f.companyId, name: "Protected physical source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await server.db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Protected folder", cwd: f.workspace }); + const [workspace] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, sourceIssueId: f.issueId, + name: "Projectless alias", cwd: f.workspace, mode: "shared_workspace", strategyType: "task_directory" }).returning(); + await server.db.update(issues).set({ projectId: null, projectWorkspaceId: null, executionWorkspaceId: workspace.id }).where(eq(issues.id, f.issueId)); + const call = (tool: string, args: Record) => f.authority.execute({ tool, arguments: args, callId: randomUUID() }); + const selection = { selection: { kind: "existing", workspaceId: workspace.id }, expectedBindingRevision: 0, requestKey: "protected-alias" }; + const repository = { repository: { kind: "url", url: "https://github.com/public/example" }, requestKey: "protected-root-write" }; + await expect(call("get_workspace", {})).resolves.toMatchObject({ workspace: { id: workspace.id, projectId: null } }); + await expect(call("select_workspace", selection)).rejects.toThrow(/protected|not authorized/); + await expect(call("prepare_repository", repository)).rejects.toThrow(/protected|not authorized/); + await server.db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "agent", principalId: f.agentId, + membershipRole: "member", status: "active" }); + await server.db.insert(principalPermissionGrants).values([ + { companyId: f.companyId, principalType: "agent", principalId: f.agentId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + { companyId: f.companyId, principalType: "user", principalId: f.userId!, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + ]); + await expect(call("select_workspace", selection)).resolves.toMatchObject({ kind: "scheduled" }); + await expect(call("prepare_repository", repository)).resolves.toMatchObject({ kind: "requires_next_admission" }); + }); + + it("denies new public clones for unlinked full-auto chat while keeping workspace discovery readable", async () => { + const f = await server.fixture({ conversation: true, disableWakeOnDemand: true }); + const cwd = await mkdtemp(join(dirname(f.workspace), "unlinked-task-")); + const [workspace] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, sourceIssueId: f.issueId, + name: "Unlinked chat files", cwd, mode: "isolated_workspace", strategyType: "task_directory" }).returning(); + const policy = { trustPreset: "low_trust_review", authorizationPolicy: { trustPreset: "low_trust_review", + trustBoundary: { mode: "low_trust_review", companyId: f.companyId, rootIssueId: f.issueId } } }; + await server.db.update(agents).set({ adapterConfig: { provider: "codex", codexPermissionMode: "never" } }).where(eq(agents.id, f.agentId)); + await server.db.update(issues).set({ projectId: null, projectWorkspaceId: null, executionWorkspaceId: workspace.id, + executionPolicy: {} }).where(eq(issues.id, f.issueId)); + await server.db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }).where(eq(heartbeatRuns.id, f.runId)); + const call = (tool: string, args: Record) => f.authority.execute({ tool, arguments: args, callId: randomUUID() }); + await expect(call("get_workspace", {})).resolves.toMatchObject({ workspace: { id: workspace.id } }); + await expect(call("prepare_repository", { repository: { kind: "url", url: "https://github.com/public/example" }, requestKey: "unlinked-full-auto" })) + .rejects.toThrow(/trusted task/); + expect(await server.db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.companyId, f.companyId))).toHaveLength(0); + }); + + it("does not expose repository preparation or workspace selection to Ask mode", async () => { + const f = await server.fixture({ mode: "ask", conversation: true, disableWakeOnDemand: true }); + await expect(f.authority.execute({ tool: "prepare_repository", callId: randomUUID(), arguments: { repository: { kind: "url", url: "https://github.com/public/example" }, requestKey: "denied" } })).rejects.toThrow(); + await expect(f.authority.execute({ tool: "select_workspace", callId: randomUUID(), arguments: { selection: { kind: "task_directory" }, expectedBindingRevision: 0, requestKey: "denied" } })).rejects.toThrow(); + }); + it("lists shared and isolated task selections and excludes unavailable lifecycle states", async () => { + const f = await server.fixture({ conversation: true, disableWakeOnDemand: true }); + const definitions = [ + { name: "Shared active", mode: "shared_workspace", status: "active", closedAt: null }, + { name: "Shared idle", mode: "shared_workspace", status: "idle", closedAt: null }, + { name: "Isolated idle", mode: "isolated_workspace", status: "idle", closedAt: null }, + { name: "In review", mode: "isolated_workspace", status: "in_review", closedAt: null }, + { name: "Archived", mode: "shared_workspace", status: "archived", closedAt: null }, + { name: "Closed", mode: "shared_workspace", status: "active", closedAt: new Date() }, + ]; + const rows = await server.db.insert(executionWorkspaces).values(definitions.map(definition => ({ + ...definition, companyId: f.companyId, projectId: null, strategyType: "task_directory", cwd: f.workspace, + }))).returning(); + const call = (tool: string, args: Record) => f.authority.execute({ tool, arguments: args, callId: randomUUID() }); + const listed = await call("list_workspaces", {}) as Array<{ id: string }>; + const candidates = new Set(rows.slice(0, 3).map(row => row.id)); + expect(listed.filter(row => rows.some(candidate => candidate.id === row.id)).map(row => row.id).sort()).toEqual([...candidates].sort()); + for (const row of rows) { + const view = await call("get_workspace", {}) as { bindingRevision: number }; + const selection = call("select_workspace", { selection: { kind: "existing", workspaceId: row.id }, + expectedBindingRevision: view.bindingRevision, requestKey: `select-${row.id}` }); + if (candidates.has(row.id)) await expect(selection).resolves.toMatchObject({ kind: "scheduled" }); + else await expect(selection).rejects.toThrow("Workspace is unavailable or inaccessible"); + } + }); + it("keeps source authorization on projectless task workspace inspection, selection, and preparation", async () => { + const f = await server.fixture({ conversation: true, disableWakeOnDemand: true }); + await server.db.update(issues).set({ projectId: null, projectWorkspaceId: null }).where(eq(issues.id, f.issueId)); + const [source] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, + projectId: f.projectId, projectWorkspaceId: f.projectWorkspaceId, mode: "shared_workspace", strategyType: "project_primary", cwd: f.workspace, name: "Source project" }).returning(); + const [unrelated] = await server.db.insert(executionWorkspaces).values({ companyId: f.companyId, + projectId: null, sourceIssueId: f.blockerId, mode: "shared_workspace", strategyType: "task_directory", cwd: f.workspace, name: "Unrelated task files" }).returning(); + await server.db.update(issues).set({ projectId: null }).where(eq(issues.id, f.blockerId)); + const boundary = { mode: "low_trust_review", companyId: f.companyId, rootIssueId: f.issueId }; + const policy = (projectIds: string[]) => ({ trustPreset: "low_trust_review", authorizationPolicy: { trustBoundary: { ...boundary, projectIds } } }); + await server.db.update(agents).set({ permissions: policy([]) }).where(eq(agents.id, f.agentId)); + const call = (tool: string, args: Record) => f.authority.execute({ tool, arguments: args, callId: randomUUID() }); + await expect(call("list_workspaces", {})).rejects.toThrow(/outside.*boundary|company-wide|privileged/i); + for (const selection of [{ kind: "existing", workspaceId: source.id }, + { kind: "configured_source", projectWorkspaceId: f.projectWorkspaceId, mode: "shared" }, + { kind: "existing", workspaceId: unrelated.id }]) { + await expect(call("select_workspace", { selection, expectedBindingRevision: 0, requestKey: randomUUID() })).rejects.toThrow(/outside.*boundary/i); + } + await server.db.update(issues).set({ executionWorkspaceId: source.id }).where(eq(issues.id, f.issueId)); + await expect(call("get_workspace", {})).rejects.toThrow(/outside.*boundary/i); + const repository = { repository: { kind: "url", url: "https://github.com/public/example" }, requestKey: "source-denied" }; + await expect(call("prepare_repository", repository)).rejects.toThrow(/outside.*boundary/i); + expect(await server.db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.companyId, f.companyId))).toHaveLength(0); + await server.db.update(agents).set({ permissions: policy([f.projectId]) }).where(eq(agents.id, f.agentId)); + await expect(call("get_workspace", {})).resolves.toMatchObject({ workspace: { id: source.id, projectId: f.projectId } }); + await expect(call("select_workspace", { selection: { kind: "existing", workspaceId: source.id }, expectedBindingRevision: 0, requestKey: "source-allowed" })).rejects.toThrow(/outside.*boundary/i); + await expect(call("prepare_repository", repository)).rejects.toThrow(/outside.*boundary/i); + expect(await server.db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.companyId, f.companyId))).toHaveLength(0); + const unrelatedCwd = await mkdtemp(join(dirname(f.workspace), "unrelated-workspace-")); + await server.db.update(executionWorkspaces).set({ cwd: unrelatedCwd }).where(eq(executionWorkspaces.id, unrelated.id)); + await expect(call("select_workspace", { selection: { kind: "existing", workspaceId: source.id }, expectedBindingRevision: 0, requestKey: "source-allowed" })).resolves.toMatchObject({ kind: "scheduled" }); + await expect(call("prepare_repository", repository)).rejects.toThrow(/trusted task/); + expect((await server.db.select().from(issues).where(eq(issues.id, f.issueId)))[0]?.projectId).toBeNull(); + }); +}); diff --git a/server/src/__tests__/task-workspace-binding.test.ts b/server/src/__tests__/task-workspace-binding.test.ts new file mode 100644 index 00000000000..2b2551c8dd3 --- /dev/null +++ b/server/src/__tests__/task-workspace-binding.test.ts @@ -0,0 +1,884 @@ +import { findNativeChatWorkspaceScope } from "../services/native-runtime/native-chat-workspace.js"; +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { assertGitSensitiveAdapterWorkspaceValid, createHeartbeatWorkspaceResolver } from "../services/heartbeat/workspaces.js"; +import { resolveDefaultAgentWorkspaceDir } from "../home-paths.js"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { eq } from "drizzle-orm"; +import express from "express"; +import request from "supertest"; +import { taskWorkspaceRoutes } from "../routes/task-workspaces.js"; +import { issueRoutes } from "../routes/issues.js"; +import { errorHandler } from "../middleware/error-handler.js"; +import { agents, authUsers, companies, companyMemberships, completionContracts, createDb, environmentLeases, executionWorkspaceRepositories, executionWorkspaceRepositoryAccess, executionWorkspaces, heartbeatRuns, issues, nativeRunFinalizations, nativeRunResults, workspaceOperations, principalPermissionGrants, projects, projectWorkspaces } from "@paperclipai/db"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { authorizationService, canActorReadExecutionWorkspace } from "../services/authorization.js"; +import { assertChatExecutionDefaultsAccess } from "../services/chat-execution-defaults.js"; +import { assertTaskWorkspaceAccess } from "../services/task-workspace-source-access.js"; +import { deleteCompany } from "../services/company-deletion.js"; +import { issueService } from "../services/issues.js"; +import { instanceSettingsService } from "../services/instance-settings.js"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("task-owned workspace bindings", () => { + let temporary: Awaited>; + let db: ReturnType; + beforeAll(async () => { + temporary = await startEmbeddedPostgresTestDatabase("paperclip-task-workspace-"); + db = createDb(temporary.connectionString); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterAll(async () => { await temporary?.cleanup(); }); + + async function fixture() { + const companyId = randomUUID(), issueId = randomUUID(), workspaceId = randomUUID(); + await db.insert(companies).values({ id: companyId, name: "Task files", issuePrefix: `T${companyId.slice(0, 6)}` }); + await db.insert(issues).values({ id: issueId, companyId, title: "Write a report" }); + await db.insert(executionWorkspaces).values({ id: workspaceId, companyId, projectId: null, sourceIssueId: issueId, + mode: "shared_workspace", strategyType: "project_primary", name: "Task files", cwd: `/tmp/task-${issueId}` }); + return { companyId, issueId, workspaceId, actor: { type: "board" as const, source: "local_implicit" as const, isInstanceAdmin: true } }; + } + + it("persists a projectless binding, preserves it on project edits, and lists it in overview", async () => { + const f = await fixture(), svc = executionWorkspaceService(db); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const projectId = randomUUID(); + await db.insert(projects).values({ id: projectId, companyId: f.companyId, name: "Reports" }); + const updated = await issueService(db).update(f.issueId, { projectId }); + expect(updated?.executionWorkspaceId).toBe(f.workspaceId); + const view = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(view.workspace?.projectId).toBeNull(); + expect(view.workspace?.cwd).toBe(`/tmp/task-${f.issueId}`); + expect(view.bindingRevision).toBe(1); + const overview = await svc.listOverview(f.companyId, { limit: 20, offset: 0 }); + expect(overview.items).toEqual(expect.arrayContaining([expect.objectContaining({ workspaceId: f.workspaceId, projectId: null })])); + }); + + it.each(["replace", "clear"] as const)("retains unbound task privacy and old root ownership when an update %ss its source", async action => { + const f = await fixture(), service = executionWorkspaceService(db); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "workspace-update-provenance-")); + try { + const oldCwd = path.join(root, "old"), newCwd = path.join(root, "new"); + await fs.mkdir(oldCwd); + await fs.mkdir(newCwd); + await fs.writeFile(path.join(oldCwd, "private.txt"), "Retained old files"); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Original source policy" }).returning(); + const [ancestor, replacement] = await db.insert(issues).values([ + { companyId: f.companyId, title: "Earlier private source" }, + { companyId: f.companyId, title: "Replacement task" }, + ]).returning(); + await db.update(executionWorkspaces).set({ sourceIssueId: ancestor.id }) + .where(eq(executionWorkspaces.id, f.workspaceId)); + await db.update(executionWorkspaces).set({ cwd: oldCwd, projectId: project.id, sourceIssueId: f.issueId }) + .where(eq(executionWorkspaces.id, f.workspaceId)); + const changed = await service.update(f.workspaceId, { cwd: newCwd, projectId: null, + sourceIssueId: action === "replace" ? replacement.id : null, + metadata: { label: "replacement metadata", _issuePrivacySources: {} }, + }); + expect(changed).toMatchObject({ cwd: newCwd, projectId: null, metadata: { + label: "replacement metadata", _issuePrivacySources: { [ancestor.id]: true, [f.issueId]: true }, + } }); + const retained = await db.select().from(executionWorkspaceRepositoryAccess) + .where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(retained.map(entry => ({ kind: entry.kind, scopeId: entry.scopeId, cwd: entry.canonicalCwd }))) + .toEqual(expect.arrayContaining([ + { kind: "source", scopeId: f.workspaceId, cwd: await fs.realpath(oldCwd) }, + { kind: "project_source", scopeId: project.id, cwd: await fs.realpath(oldCwd) }, + ])); + expect(retained.every(entry => ["source", "project_source", "shared_project_source"].includes(entry.kind))).toBe(true); + expect(await fs.readFile(path.join(oldCwd, "private.txt"), "utf8")).toBe("Retained old files"); + expect(await service.update(f.workspaceId, { metadata: null })).toMatchObject({ + metadata: { _issuePrivacySources: { [ancestor.id]: true, [f.issueId]: true } }, + }); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + + it("rolls back a source update when existing root provenance cannot be verified", async () => { + const f = await fixture(), service = executionWorkspaceService(db); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "workspace-update-invalid-root-")); + try { + const oldCwd = path.join(root, "not-a-directory"); + await fs.writeFile(oldCwd, "Unverifiable former root"); + await db.update(executionWorkspaces).set({ cwd: oldCwd, metadata: { label: "original" } }) + .where(eq(executionWorkspaces.id, f.workspaceId)); + const [before] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)); + await expect(service.update(f.workspaceId, { cwd: path.join(root, "new"), sourceIssueId: null, metadata: null })) + .rejects.toThrow("Workspace files cannot be detached from unverifiable provenance"); + const [after] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)); + expect(after).toEqual(before); + expect(await db.select().from(executionWorkspaceRepositoryAccess) + .where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId))).toEqual([]); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + + it.each(["explicit", "channel"] as const)("persists the canonical null binding for a %s task-directory selection", async (source) => { + const f = await fixture(), settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Legacy source" }).returning(); + const [workspace] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, + name: "Legacy folder", cwd: "/tmp/legacy-folder" }).returning(); + const created = await issueService(db).create(f.companyId, { + title: "Use a new task folder", projectId: project.id, + projectWorkspaceId: workspace.id, executionWorkspaceId: f.workspaceId, + executionWorkspacePreference: "reuse_existing", executionWorkspaceSettings: { mode: "isolated_workspace" }, + workspaceSelection: { kind: "task_directory" }, workspaceSelectionSource: source, workspaceSelectionActor: f.actor, + }); + const [stored] = await db.select().from(issues).where(eq(issues.id, created.id)); + expect(stored).toMatchObject({ projectId: project.id, projectWorkspaceId: null, executionWorkspaceId: null, + executionWorkspacePreference: null, executionWorkspaceSettings: { mode: "shared_workspace" }, + workspaceSelection: { selection: { kind: "task_directory" }, source } }); + expect((await executionWorkspaceService(db).inspectTaskWorkspace(f.companyId, created.id, f.actor)).workspace).toBeNull(); + } finally { + await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); + } + }); + + it.each(["create", "createChild"] as const)("inherits a parent task folder through %s with isolated workspaces disabled", async (entryPoint) => { + const f = await fixture(), settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: false }); + try { + await executionWorkspaceService(db).bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const tasks = issueService(db); + const input = { title: "Continue parent work", workspaceSelectionActor: f.actor, + workspaceSelection: { kind: "task_directory" as const }, workspaceSelectionSource: "channel" as const }; + const child = entryPoint === "create" + ? await tasks.create(f.companyId, { ...input, parentId: f.issueId }) + : (await tasks.createChild(f.issueId, input)).issue; + expect(child).toMatchObject({ parentId: f.issueId, projectId: null, executionWorkspaceId: f.workspaceId, + executionWorkspacePreference: "reuse_existing" }); + const view = await executionWorkspaceService(db).inspectTaskWorkspace(f.companyId, child.id, f.actor); + expect(view.workspace).toMatchObject({ id: f.workspaceId, cwd: `/tmp/task-${f.issueId}` }); + const explicit = await tasks.create(f.companyId, { ...input, title: "Explicit separate folder", parentId: f.issueId, + workspaceSelectionSource: "explicit" }); + expect(explicit.executionWorkspaceId).toBeNull(); + } finally { + await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); + } + }); + + it.each([ + { entryPoint: "create", parentSource: "task_directory" }, + { entryPoint: "createChild", parentSource: "task_directory" }, + { entryPoint: "create", parentSource: "configured_source" }, + { entryPoint: "createChild", parentSource: "configured_source" }, + ] as const)("honors a child's explicit source through $entryPoint over its parent's $parentSource binding", async ({ entryPoint, parentSource }) => { + const f = await fixture(), tasks = issueService(db), workspaces = executionWorkspaceService(db); + const [sourceProject] = await db.insert(projects).values({ companyId: f.companyId, name: "Filesystem sources" }).returning(); + const [parentConfigured, childConfigured] = await db.insert(projectWorkspaces).values([ + { companyId: f.companyId, projectId: sourceProject.id, name: "Parent files", cwd: `/tmp/parent-${f.issueId}` }, + { companyId: f.companyId, projectId: sourceProject.id, name: "Child files", cwd: `/tmp/child-${f.issueId}` }, + ]).returning(); + if (parentSource === "configured_source") { + await db.update(executionWorkspaces).set({ projectId: sourceProject.id, projectWorkspaceId: parentConfigured.id }) + .where(eq(executionWorkspaces.id, f.workspaceId)); + } + await workspaces.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const parentBefore = await workspaces.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + const input = { title: "Use child-specific files", projectWorkspaceId: childConfigured.id, workspaceSelectionActor: f.actor }; + const child = entryPoint === "create" + ? await tasks.create(f.companyId, { ...input, parentId: f.issueId }) + : (await tasks.createChild(f.issueId, input)).issue; + expect(child).toMatchObject({ parentId: f.issueId, projectId: null, projectWorkspaceId: childConfigured.id, + executionWorkspaceId: null, executionWorkspacePreference: null }); + expect((await workspaces.inspectTaskWorkspace(f.companyId, child.id, f.actor)).workspace).toBeNull(); + const parentAfter = await workspaces.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(parentAfter.workspace).toEqual(parentBefore.workspace); + expect(parentAfter.bindingRevision).toBe(parentBefore.bindingRevision); + }); + + it.each(["create", "createChild", "strategy_only"] as const)("preserves an explicit null source through %s while applying organizational defaults", async entryPoint => { + const f = await fixture(), tasks = issueService(db), workspaces = executionWorkspaceService(db); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Organizational defaults" }).returning(); + const [defaultSource, parentSource] = await db.insert(projectWorkspaces).values([ + { companyId: f.companyId, projectId: project.id, name: "Default files", cwd: `/tmp/default-${f.issueId}`, isPrimary: true }, + { companyId: f.companyId, projectId: project.id, name: "Parent files", cwd: `/tmp/parent-${f.issueId}` }, + ]).returning(); + await db.update(issues).set({ projectId: project.id }).where(eq(issues.id, f.issueId)); + await db.update(executionWorkspaces).set({ projectId: project.id, projectWorkspaceId: parentSource.id }) + .where(eq(executionWorkspaces.id, f.workspaceId)); + await workspaces.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const input = { title: "Clear parent workspace choice", projectWorkspaceId: null, workspaceSelectionActor: f.actor }; + const child = entryPoint === "create" + ? await tasks.create(f.companyId, { ...input, parentId: f.issueId }) + : (await tasks.createChild(f.issueId, { ...input, + ...(entryPoint === "strategy_only" ? { executionWorkspaceInheritanceMode: "strategy_only" as const } : {}), + })).issue; + expect(child).toMatchObject({ projectId: project.id, projectWorkspaceId: defaultSource.id, + executionWorkspaceId: null, executionWorkspacePreference: null, workspaceSelection: null }); + expect((await workspaces.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).workspace) + .toMatchObject({ id: f.workspaceId, projectWorkspaceId: parentSource.id }); + }); + + it.each(["explicit", "channel", "inherited", "legacy"] as const)("keeps a reused task folder's null source authoritative for %s creation", async (source) => { + const f = await fixture(), svc = executionWorkspaceService(db), tasks = issueService(db); + const settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Organization with unrelated source" }).returning(); + const [projectWorkspace] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, + name: "Unrelated repository", cwd: "/tmp/unrelated-source", isPrimary: true }).returning(); + let parentId: string | undefined; + if (source === "inherited") { + const [parent] = await db.insert(issues).values({ companyId: f.companyId, projectId: project.id, + title: "Parent sharing task files", executionWorkspaceId: f.workspaceId }).returning(); + parentId = parent.id; + } + const created = await tasks.create(f.companyId, { + title: "Reuse files under an organizational project", projectId: project.id, parentId, + workspaceSelectionActor: f.actor, + ...(source === "legacy" + ? { executionWorkspaceId: f.workspaceId, projectWorkspaceId: projectWorkspace.id, executionWorkspacePreference: "reuse_existing" } + : source === "inherited" ? {} + : { workspaceSelection: { kind: "existing" as const, workspaceId: f.workspaceId }, workspaceSelectionSource: source }), + }); + expect(created).toMatchObject({ projectId: project.id, executionWorkspaceId: f.workspaceId, projectWorkspaceId: null }); + const [original] = await db.select().from(issues).where(eq(issues.id, f.issueId)); + expect(original).toMatchObject({ projectId: null, projectWorkspaceId: null, executionWorkspaceId: f.workspaceId }); + const { workspace } = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(workspace).toMatchObject({ projectId: null, projectWorkspaceId: null, cwd: `/tmp/task-${f.issueId}` }); + for (const task of [created, original]) { + await expect(assertGitSensitiveAdapterWorkspaceValid({ + adapterType: "codex_local", agentId: randomUUID(), issue: task, + resolvedWorkspace: { cwd: workspace!.cwd!, source: "task_session", projectId: null, workspaceId: null, + repoUrl: null, repoRef: null, workspaceHints: [], warnings: [] }, + executionWorkspace: { cwd: workspace!.cwd!, baseCwd: workspace!.cwd!, source: "task_session", projectId: null, + workspaceId: null, repoUrl: null, repoRef: null, strategy: "project_primary", branchName: null, + worktreePath: null, warnings: [], created: false, branchCreatedByRuntime: false }, + persistedExecutionWorkspace: workspace, executionTarget: { kind: "local" }, + })).resolves.toBeUndefined(); + } + } finally { + await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); + } + }); + + it("replaces an old source with the selected binding's null source on an ordinary update", async () => { + const f = await fixture(), svc = executionWorkspaceService(db), settings = instanceSettingsService(db); + const previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Organizational project" }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, + name: "Previous source", cwd: "/tmp/previous-source" }).returning(); + const [oldWorkspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: project.id, + projectWorkspaceId: source.id, name: "Previous folder", cwd: source.cwd, mode: "shared_workspace", strategyType: "project_primary" }).returning(); + const [task] = await db.insert(issues).values({ companyId: f.companyId, projectId: project.id, + projectWorkspaceId: source.id, executionWorkspaceId: oldWorkspace.id, title: "Switch to task files" }).returning(); + await svc.selectTaskWorkspace({ ...f, issueId: task.id, selection: { kind: "task_directory" }, + expectedBindingRevision: 0, requestKey: "superseded-before-raw-binding" }); + const updated = await issueService(db).update(task.id, { executionWorkspaceId: f.workspaceId }); + expect(updated).toMatchObject({ projectId: project.id, executionWorkspaceId: f.workspaceId, projectWorkspaceId: null, + workspaceBindingRevision: 2, workspacePendingSelection: null }); + expect(await svc.applyPendingTaskWorkspaceSelection({ ...f, issueId: task.id, runId: randomUUID() })).toBe(false); + expect((await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).workspace) + .toMatchObject({ id: f.workspaceId, projectId: null, projectWorkspaceId: null, cwd: `/tmp/task-${f.issueId}` }); + const [previousWorkspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, oldWorkspace.id)); + expect(previousWorkspace).toMatchObject({ projectId: project.id, projectWorkspaceId: source.id, cwd: source.cwd }); + } finally { + await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); + } + }); + + it("queues explicit root changes without changing the current binding and applies at admission", async () => { + const f = await fixture(), svc = executionWorkspaceService(db); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const request = { ...f, selection: { kind: "task_directory" as const }, expectedBindingRevision: 1, requestKey: "choose-task-directory" }; + expect(await svc.selectTaskWorkspace(request)).toMatchObject({ kind: "scheduled", applies: "next_normal_admission" }); + expect(await svc.selectTaskWorkspace(request)).toMatchObject({ kind: "scheduled" }); + await expect(svc.selectTaskWorkspace({ ...request, selection: { kind: "existing", workspaceId: f.workspaceId } })).rejects.toThrow("different intent"); + expect((await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).workspace?.id).toBe(f.workspaceId); + await svc.applyPendingTaskWorkspaceSelection({ ...f, runId: randomUUID() }); + const view = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(view.workspace).toBeNull(); + expect(view.selection).toMatchObject({ selection: { kind: "task_directory" } }); + expect(view.bindingRevision).toBe(3); + expect(await svc.selectTaskWorkspace(request)).toMatchObject({ kind: "applied", bindingRevision: 3 }); + expect((await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)))[0]?.metadata) + .toMatchObject({ _issuePrivacySources: { [f.issueId]: true } }); + await expect(svc.selectTaskWorkspace({ ...request, requestKey: "stale" })).rejects.toThrow("binding changed"); + }); + + it.each(["before admission", "after admission"] as const)("rejects a superseded selection retry %s without replacing the newer choice", async phase => { + const f = await fixture(), svc = executionWorkspaceService(db); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const [nextWorkspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, + mode: "shared_workspace", strategyType: "project_primary", name: "Newer choice", cwd: `/tmp/newer-${f.issueId}` }).returning(); + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Running worker" }).returning(); + const immutableInput = { cwd: `/tmp/task-${f.issueId}`, binding: { executionWorkspaceId: f.workspaceId } }; + const [run] = await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: agent.id, + status: "running", contextSnapshot: { issueId: f.issueId, persistedNativeExecutionInput: immutableInput } }).returning(); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = f.actor; next(); }); + app.use("/api", taskWorkspaceRoutes(db)); + app.use(errorHandler); + const select = (body: object) => request(app).put(`/api/issues/${f.issueId}/workspace`).send(body); + const first = { selection: { kind: "task_directory" }, expectedBindingRevision: 1, requestKey: "choice-A" }; + const acceptedA = await select(first); + expect(acceptedA.status).toBe(200); + const second = { selection: { kind: "existing", workspaceId: nextWorkspace.id }, + expectedBindingRevision: acceptedA.body.bindingRevision, requestKey: "choice-B" }; + const acceptedB = await select(second); + expect(acceptedB.status).toBe(200); + const admission = { ...f, runId: randomUUID() }; + await expect(svc.applyPendingTaskWorkspaceSelection(admission)).rejects.toThrow("Previous task run must finish"); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ + workspace: { id: f.workspaceId }, pendingSelection: { requestKey: second.requestKey }, + }); + expect((await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, run.id)))[0]).toMatchObject({ + status: "running", contextSnapshot: { persistedNativeExecutionInput: immutableInput }, + }); + if (phase === "after admission") { + await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, run.id)); + expect(await svc.applyPendingTaskWorkspaceSelection(admission)).toBe(true); + } + const beforeRetry = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + const retryA = await select(first); + expect(retryA.status).toBe(409); + expect(retryA.body.error).toContain("binding changed"); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toEqual(beforeRetry); + const retryB = await select(second); + expect(retryB.status).toBe(200); + expect(retryB.body.kind).toBe(phase === "before admission" ? "scheduled" : "applied"); + if (phase === "before admission") { + await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, run.id)); + expect(await svc.applyPendingTaskWorkspaceSelection(admission)).toBe(true); + } + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ + workspace: { id: nextWorkspace.id }, selection: { request: { key: second.requestKey } }, pendingSelection: null, + }); + }); + + describe.each(["pending selection", "legacy update"] as const)("native settlement before %s", (entryPoint) => { + let isolatedWorkspacesEnabled: boolean; + beforeAll(async () => { + isolatedWorkspacesEnabled = (await instanceSettingsService(db).getExperimental()).enableIsolatedWorkspaces; + await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: true }); + }); + afterAll(async () => { await instanceSettingsService(db).updateExperimental({ enableIsolatedWorkspaces: isolatedWorkspacesEnabled }); }); + async function settledNativeTask(failureCode = "native_retry_cancelled", acceptedResult = false) { + const f = await fixture(), svc = executionWorkspaceService(db); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Stopped worker" }).returning(); + const [run] = await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: agent.id, + nativeIssueId: f.issueId, runtimeMode: "native", nativePhase: "terminal_failure", status: "cancelled", + contextSnapshot: {}, finishedAt: new Date() }).returning(); + let resultId: string | null = null; + if (acceptedResult) { + const [contract] = await db.insert(completionContracts).values({ companyId: f.companyId, issueId: f.issueId, + revision: 1, schemaVersion: "paperclip.completion-contract.v1", policyVersion: "test", risk: "low", + completionAuthority: "agent", incompleteCriteriaPolicy: "block", contractJson: {}, canonicalSha256: "contract", + createdByActorType: "agent", createdByActorId: agent.id }).returning(); + await db.update(heartbeatRuns).set({ completionContractId: contract.id }).where(eq(heartbeatRuns.id, run.id)); + const [result] = await db.insert(nativeRunResults).values({ companyId: f.companyId, issueId: f.issueId, runId: run.id, + completionContractId: contract.id, serverFingerprint: randomUUID(), schemaStatus: "accepted", resultJson: {}, canonicalSha256: "result" }).returning(); + resultId = result.id; + } + await db.insert(nativeRunFinalizations).values({ companyId: f.companyId, issueId: f.issueId, runId: run.id, + phase: "terminal_failure", failureCode, resultId }); + const [lease] = await db.insert(environmentLeases).values({ companyId: f.companyId, issueId: f.issueId, + heartbeatRunId: run.id, executionWorkspaceId: f.workspaceId, status: "released", cleanupStatus: "success", releasedAt: new Date() }).returning(); + if (entryPoint === "pending selection") await svc.selectTaskWorkspace({ ...f, selection: { kind: "task_directory" }, expectedBindingRevision: 1, requestKey: "after-stop" }); + const change = () => entryPoint === "pending selection" + ? svc.applyPendingTaskWorkspaceSelection({ ...f, runId: randomUUID() }) + : issueService(db).update(f.issueId, { executionWorkspaceId: null }); + return { ...f, run, lease, svc, change }; + } + + it.each(["native_retry_cancelled", "native_permission_declined"])("allows a settled %s without deleting its historical failure", async (failureCode) => { + const f = await settledNativeTask(failureCode); + await f.change(); + expect(await f.svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ workspace: null, bindingRevision: entryPoint === "pending selection" ? 3 : 2, pendingSelection: null }); + expect((await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.run.id)))[0]) + .toMatchObject({ phase: "terminal_failure", failureCode }); + }); + + it.each(["running native owner", "coordinator owner", "applied result", "pending cleanup", "failed cleanup", "export marker", "active lease", "failed output", "accepted output"] as const)("keeps %s fenced until its durable obligation settles", async (obligation) => { + const f = await settledNativeTask("native_retry_cancelled", obligation === "accepted output"); + if (obligation === "running native owner") await db.update(heartbeatRuns).set({ status: "running" }).where(eq(heartbeatRuns.id, f.run.id)); + if (obligation === "coordinator owner") await db.update(nativeRunFinalizations).set({ leaseOwner: "controller" }).where(eq(nativeRunFinalizations.runId, f.run.id)); + if (obligation === "applied result") await db.update(nativeRunFinalizations).set({ phase: "applied" }).where(eq(nativeRunFinalizations.runId, f.run.id)); + if (obligation === "pending cleanup") await db.update(environmentLeases).set({ status: "pending_cleanup" }).where(eq(environmentLeases.id, f.lease.id)); + if (obligation === "failed cleanup") await db.update(environmentLeases).set({ cleanupStatus: "failed" }).where(eq(environmentLeases.id, f.lease.id)); + if (obligation === "export marker") await db.update(environmentLeases).set({ metadata: { nativeWorkspaceExportResume: { runId: f.run.id } } }).where(eq(environmentLeases.id, f.lease.id)); + if (obligation === "active lease") await db.update(environmentLeases).set({ status: "active", releasedAt: null }).where(eq(environmentLeases.id, f.lease.id)); + if (obligation === "failed output") await db.insert(workspaceOperations).values({ companyId: f.companyId, issueId: f.issueId, + heartbeatRunId: f.run.id, executionWorkspaceId: f.workspaceId, phase: "workspace_finalize", status: "failed" }); + await expect(f.change()).rejects.toThrow(/workspace|Previous task run/); + expect(await f.svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ workspace: { id: f.workspaceId }, bindingRevision: entryPoint === "pending selection" ? 2 : 1 }); + if (obligation === "failed output" || obligation === "accepted output") { + await db.update(workspaceOperations).set({ createdAt: new Date(0) }).where(eq(workspaceOperations.heartbeatRunId, f.run.id)); + await db.insert(workspaceOperations).values({ companyId: f.companyId, issueId: f.issueId, + heartbeatRunId: f.run.id, executionWorkspaceId: f.workspaceId, phase: "workspace_finalize", status: "succeeded", finishedAt: new Date() }); + await f.change(); + expect(await f.svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ workspace: null, bindingRevision: entryPoint === "pending selection" ? 3 : 2 }); + } + }); + + it.each(["warm release", "unsafe export omission"] as const)("accepts a settled %s receipt", async (receipt) => { + const f = await settledNativeTask("native_retry_cancelled", receipt === "unsafe export omission"); + if (receipt === "warm release") await db.update(environmentLeases).set({ releasedAt: null, status: "retained", leasePolicy: "reuse_by_environment", cleanupStatus: "success" }).where(eq(environmentLeases.id, f.lease.id)); + if (receipt === "unsafe export omission") await db.insert(workspaceOperations).values({ companyId: f.companyId, issueId: f.issueId, + heartbeatRunId: f.run.id, executionWorkspaceId: f.workspaceId, phase: "workspace_finalize", status: "succeeded", + metadata: { workspaceSync: { omitted: true } }, finishedAt: new Date() }); + await f.change(); + expect(await f.svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ workspace: null, bindingRevision: entryPoint === "pending selection" ? 3 : 2 }); + }); + }); + + it("preserves a queued next-run choice through first binding and its original retry receipt", async () => { + const f = await fixture(), svc = executionWorkspaceService(db); + const [nextWorkspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, + mode: "shared_workspace", strategyType: "project_primary", name: "Next folder", cwd: `/tmp/next-${f.issueId}` }).returning(); + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "First worker", adapterType: "process" }).returning(); + const [run] = await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: agent.id, + status: "running", invocationSource: "on_demand", contextSnapshot: { issueId: f.issueId } }).returning(); + const selectionRequest = { ...f, selection: { kind: "existing" as const, workspaceId: nextWorkspace.id }, + expectedBindingRevision: 0, requestKey: "choose-during-first-admission" }; + await svc.selectTaskWorkspace(selectionRequest); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ + workspace: { id: f.workspaceId }, bindingRevision: 2, + pendingSelection: { expectedBindingRevision: 2, intent: { request: { expectedBindingRevision: 0 } } }, + }); + expect(await svc.selectTaskWorkspace(selectionRequest)).toMatchObject({ kind: "scheduled", bindingRevision: 2 }); + await expect(svc.selectTaskWorkspace({ ...selectionRequest, expectedBindingRevision: 1 })).rejects.toThrow("different intent"); + const admission = { ...f, runId: randomUUID() }; + await expect(svc.applyPendingTaskWorkspaceSelection(admission)).rejects.toThrow("Previous task run must finish"); + await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, run.id)); + expect(await svc.applyPendingTaskWorkspaceSelection(admission)).toBe(true); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ + workspace: { id: nextWorkspace.id }, bindingRevision: 3, pendingSelection: null, + }); + expect(await svc.selectTaskWorkspace(selectionRequest)).toMatchObject({ kind: "applied", bindingRevision: 3 }); + + await svc.selectTaskWorkspace({ ...f, selection: { kind: "task_directory" }, + expectedBindingRevision: 3, requestKey: "later-choice" }); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + await expect(svc.applyPendingTaskWorkspaceSelection({ ...f, runId: randomUUID() })).rejects.toThrow("Pending workspace selection is stale"); + }); + + it("allows regular signed-in members to select files and request repositories while denying viewers", async () => { + const f = await fixture(), svc = executionWorkspaceService(db), userId = randomUUID(); + await db.insert(authUsers).values({ id: userId, name: "Board member", email: `${userId}@example.test`, + createdAt: new Date(), updatedAt: new Date() }); + const [membership] = await db.insert(companyMemberships).values({ companyId: f.companyId, + principalType: "user", principalId: userId, status: "active", membershipRole: "member" }).returning(); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const actor = { type: "board" as const, source: "session" as const, userId, + companyIds: [f.companyId], isInstanceAdmin: false }; + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = actor; next(); }); + app.use("/api", taskWorkspaceRoutes(db)); + app.use(errorHandler); + const [assignee] = await db.insert(agents).values({ companyId: f.companyId, name: "Assigned worker", status: "idle" }).returning(); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected files", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, + name: "Source", cwd: `/tmp/task-${f.issueId}` }).returning(); + await db.update(issues).set({ assigneeAgentId: assignee.id }).where(eq(issues.id, f.issueId)); + await db.update(executionWorkspaces).set({ projectId: project.id, projectWorkspaceId: source.id }).where(eq(executionWorkspaces.id, f.workspaceId)); + const selection = { selection: { kind: "existing" as const, workspaceId: f.workspaceId }, expectedBindingRevision: 1, requestKey: "member-selection" }; + const repository = { repository: { kind: "url" as const, url: "https://github.com/public/example" }, requestKey: "member-repository" }; + expect((await request(app).put(`/api/issues/${f.issueId}/workspace`).send(selection)).status).toBe(403); + expect((await request(app).post(`/api/issues/${f.issueId}/workspace/repositories`).send(repository)).status).toBe(403); + const channelDefaults = { companyId: f.companyId, actor, assigneeAgentId: assignee.id, + defaults: { workspace: { kind: "configured_source" as const, projectWorkspaceId: source.id, mode: "shared" as const } } }; + await expect(assertChatExecutionDefaultsAccess(db, channelDefaults)).rejects.toThrow(/protected/); + await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user", principalId: userId, + permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: assignee.id } }); + await expect(assertChatExecutionDefaultsAccess(db, channelDefaults)).resolves.toBeUndefined(); + await expect(assertChatExecutionDefaultsAccess(db, { ...channelDefaults, assigneeAgentId: null })).rejects.toThrow(/protected/); + expect(await issueService(db).create(f.companyId, { title: "Board-assigned shared work", createdByUserId: userId, + assigneeAgentId: assignee.id, workspaceSelectionActor: actor, + workspaceSelection: { kind: "configured_source", projectWorkspaceId: source.id, mode: "shared" } })) + .toMatchObject({ assigneeAgentId: assignee.id, projectWorkspaceId: source.id }); + const selected = await request(app).put(`/api/issues/${f.issueId}/workspace`).send(selection); + expect(selected.status, JSON.stringify(selected.body)).toBe(200); + expect(selected.body).toMatchObject({ kind: "scheduled" }); + const prepared = await request(app).post(`/api/issues/${f.issueId}/workspace/repositories`).send(repository); + expect(prepared.status, JSON.stringify(prepared.body)).toBe(200); + expect(prepared.body).toMatchObject({ kind: "requires_next_admission" }); + const before = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(before).toMatchObject({ workspace: { id: f.workspaceId }, pendingSelection: { requestKey: selection.requestKey } }); + expect(await svc.listTaskRepositories(f.companyId, f.workspaceId)).toHaveLength(1); + + await db.update(companyMemberships).set({ membershipRole: "viewer" }).where(eq(companyMemberships.id, membership.id)); + const deniedSelection = { ...selection, requestKey: "viewer-selection" }; + const deniedRepository = { ...repository, requestKey: "viewer-repository" }; + expect((await request(app).put(`/api/issues/${f.issueId}/workspace`).send(deniedSelection)).status).toBe(403); + expect((await request(app).post(`/api/issues/${f.issueId}/workspace/repositories`).send(deniedRepository)).status).toBe(403); + await expect(svc.selectTaskWorkspace({ ...f, actor, ...deniedSelection })).rejects.toThrow("modification is not allowed"); + await expect(svc.requestTaskRepository({ ...f, actor, request: deniedRepository })).rejects.toThrow("modification is not allowed"); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toEqual(before); + expect(await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.executionWorkspaceId, f.workspaceId))).toHaveLength(1); + }); + + it.each(["binding", "configured_source"] as const)("enforces source authority on legacy PATCH %s and rolls back denied edits", async kind => { + const f = await fixture(), userId = randomUUID(), settings = instanceSettingsService(db); + const previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + await db.insert(authUsers).values({ id: userId, name: "Workspace selector", email: `${userId}@example.test`, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" }); + const [assignee, nextAssignee] = await db.insert(agents).values(["Current worker", "Next worker"].map(name => ({ companyId: f.companyId, name, status: "idle", runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: false } } }))).returning(); + await db.update(issues).set({ assigneeAgentId: assignee.id }).where(eq(issues.id, f.issueId)); + const [project, organization] = await db.insert(projects).values([ + { companyId: f.companyId, name: "Protected source", executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }, + { companyId: f.companyId, name: "Organization" }, + ]).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Source", cwd: `/tmp/source-${f.issueId}` }).returning(); + await db.update(executionWorkspaces).set({ projectId: project.id, projectWorkspaceId: source.id }).where(eq(executionWorkspaces.id, f.workspaceId)); + const actor = { type: "board" as const, source: "session" as const, userId, companyIds: [f.companyId], isInstanceAdmin: false }; + const beforeMetadata = (await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)))[0].metadata; + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = actor; next(); }); + app.use("/api", taskWorkspaceRoutes(db)); + app.use("/api", issueRoutes(db, {} as never)); + app.use(errorHandler); + const patch = kind === "binding" ? { executionWorkspaceId: f.workspaceId } : { projectWorkspaceId: source.id, executionWorkspaceSettings: { mode: "shared_workspace" } }; + const selection = kind === "binding" ? { kind: "existing", workspaceId: f.workspaceId } : { kind: "configured_source", projectWorkspaceId: source.id, mode: "shared" }; + expect((await request(app).put(`/api/issues/${f.issueId}/workspace`).send({ selection, expectedBindingRevision: 0, requestKey: "denied" })).status).toBe(403); + const denied = await request(app).patch(`/api/issues/${f.issueId}`).send(patch); + expect(denied.status, JSON.stringify(denied.body)).toBe(403); + const readTask = () => db.select().from(issues).where(eq(issues.id, f.issueId)).then(rows => rows[0]); + expect(await readTask()).toMatchObject({ executionWorkspaceId: null, projectWorkspaceId: null, workspaceBindingRevision: 0 }); + expect((await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)))[0].metadata).toEqual(beforeMetadata); + const [grant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user", principalId: userId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: assignee.id } }).returning(); + await expect(issueService(db).update(f.issueId, { ...patch, assigneeAgentId: nextAssignee.id, workspaceSelectionActor: actor })).rejects.toThrow(/protected/); + expect(await readTask()).toMatchObject({ assigneeAgentId: assignee.id, workspaceBindingRevision: 0 }); + const allowed = await request(app).patch(`/api/issues/${f.issueId}`).send(patch); + expect(allowed.status, JSON.stringify(allowed.body)).toBe(200); + const bound = await readTask(); + expect(bound).toMatchObject({ ...patch, projectWorkspaceId: source.id, workspaceBindingRevision: 1 }); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + const organized = await request(app).patch(`/api/issues/${f.issueId}`).send({ projectId: organization.id }); + expect(organized.status, JSON.stringify(organized.body)).toBe(200); + expect(await readTask()).toMatchObject({ projectId: organization.id, executionWorkspaceId: bound.executionWorkspaceId, projectWorkspaceId: source.id, workspaceBindingRevision: 1 }); + } finally { await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); } + }); + + it.each(["create", "patch", "selection"] as const)("uses the assigned agent's effective source strategy for %s authorization", async operation => { + const f = await fixture(), userId = randomUUID(), settings = instanceSettingsService(db); + const previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true, enableIsolatedWorkspacesByDefault: false }); + try { + await db.insert(authUsers).values({ id: userId, name: "Source reader", email: `${userId}@example.test`, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" }); + const [isolatedAgent, sharedAgent] = await db.insert(agents).values(["git_worktree", "project_primary"].map(type => ({ + companyId: f.companyId, name: type, status: "idle", adapterConfig: { workspaceStrategy: { type } }, + runtimeConfig: { heartbeat: { enabled: false, wakeOnDemand: false } }, + }))).returning(); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Read-only source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Source", cwd: `/tmp/source-${f.issueId}` }).returning(); + const actor = { type: "board" as const, source: "session" as const, userId, companyIds: [f.companyId], isInstanceAdmin: false }; + const sharedOverrides = { adapterConfig: { workspaceStrategy: { type: "project_primary" } } }; + const tasks = issueService(db), workspaces = executionWorkspaceService(db); + if (operation === "create") { + const input = { title: "Read source in separate checkout", assigneeAgentId: isolatedAgent.id, + projectWorkspaceId: source.id, workspaceSelectionActor: actor, createdByUserId: userId }; + await expect(tasks.create(f.companyId, { ...input, assigneeAdapterOverrides: sharedOverrides })).rejects.toThrow(/protected/); + await expect(tasks.create(f.companyId, input)).resolves.toMatchObject({ assigneeAgentId: isolatedAgent.id, projectWorkspaceId: source.id }); + await expect(tasks.create(f.companyId, { ...input, workspaceSelection: { kind: "configured_source", projectWorkspaceId: source.id, mode: "managed_isolated" }, + assigneeAdapterOverrides: sharedOverrides })).rejects.toThrow(/protected/); + } else if (operation === "patch") { + await db.update(issues).set({ assigneeAgentId: sharedAgent.id }).where(eq(issues.id, f.issueId)); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { req.actor = actor; next(); }); + app.use("/api", issueRoutes(db, {} as never)); app.use(errorHandler); + const patch = { projectWorkspaceId: source.id, assigneeAgentId: isolatedAgent.id }; + const denied = await request(app).patch(`/api/issues/${f.issueId}`).send({ ...patch, assigneeAdapterOverrides: sharedOverrides }); + expect(denied.status, JSON.stringify(denied.body)).toBe(403); + expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0]).toMatchObject({ assigneeAgentId: sharedAgent.id, projectWorkspaceId: null }); + const allowed = await request(app).patch(`/api/issues/${f.issueId}`).send(patch); + expect(allowed.status, JSON.stringify(allowed.body)).toBe(200); + expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0]).toMatchObject({ assigneeAgentId: isolatedAgent.id, projectWorkspaceId: source.id }); + } else { + await db.update(issues).set({ assigneeAgentId: isolatedAgent.id, assigneeAdapterOverrides: sharedOverrides }).where(eq(issues.id, f.issueId)); + const input = { companyId: f.companyId, issueId: f.issueId, actor, + selection: { kind: "configured_source" as const, projectWorkspaceId: source.id, mode: "managed_isolated" as const } }; + await expect(workspaces.validateSelection(input)).rejects.toThrow(/protected/); + await db.update(issues).set({ assigneeAdapterOverrides: null }).where(eq(issues.id, f.issueId)); + await expect(workspaces.validateSelection(input)).resolves.toMatchObject({ projectWorkspaceId: source.id }); + await db.update(issues).set({ assigneeAgentId: sharedAgent.id }).where(eq(issues.id, f.issueId)); + await expect(workspaces.validateSelection(input)).rejects.toThrow(/protected/); + } + } finally { await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces, enableIsolatedWorkspacesByDefault: previous.enableIsolatedWorkspacesByDefault }); } + }); + + it("requires source project assignment authority for shared files while preserving isolated reads", async () => { + const f = await fixture(), svc = executionWorkspaceService(db), tasks = issueService(db); + const settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Read-only source user", adapterType: "process", status: "idle" }).returning(); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "agent", principalId: agent.id, status: "active", membershipRole: "member" }); + const actor = { type: "agent" as const, agentId: agent.id, companyId: f.companyId, source: "agent_key" as const }; + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, + name: "Shared source", cwd: `/tmp/protected-${f.issueId}` }).returning(); + const [shared] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: project.id, + projectWorkspaceId: source.id, name: "Shared files", cwd: source.cwd, mode: "shared_workspace", strategyType: "project_primary" }).returning(); + const [isolated] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: project.id, + projectWorkspaceId: source.id, name: "Isolated files", cwd: `/tmp/isolated-${f.issueId}`, mode: "isolated_workspace", strategyType: "git_worktree" }).returning(); + expect(await authorizationService(db).decide({ actor, action: "project:read", + resource: { type: "project", companyId: f.companyId, projectId: project.id } })).toMatchObject({ allowed: true }); + const sharedSelection = { kind: "configured_source" as const, projectWorkspaceId: source.id, mode: "shared" as const }; + const isolatedSelection = { ...sharedSelection, mode: "managed_isolated" as const }; + await expect(svc.validateSelection({ ...f, actor, selection: sharedSelection })).rejects.toThrow(/protected/); + await expect(svc.validateSelection({ ...f, actor, selection: isolatedSelection })).resolves.toMatchObject({ executionWorkspaceSettings: { mode: "isolated_workspace" } }); + await expect(svc.validateSelection({ ...f, actor, selection: { kind: "existing", workspaceId: shared.id } })).rejects.toThrow(/protected/); + await expect(svc.validateSelection({ ...f, actor, selection: { kind: "existing", workspaceId: isolated.id } })).resolves.toMatchObject({ executionWorkspaceId: isolated.id }); + for (const sourceKind of ["explicit", "channel"] as const) { + await expect(tasks.create(f.companyId, { title: `Denied ${sourceKind}`, createdByAgentId: agent.id, + workspaceSelection: sharedSelection, workspaceSelectionSource: sourceKind, workspaceSelectionActor: actor })).rejects.toThrow(/protected/); + expect(await tasks.create(f.companyId, { title: `Isolated ${sourceKind}`, createdByAgentId: agent.id, + workspaceSelection: isolatedSelection, workspaceSelectionSource: sourceKind, workspaceSelectionActor: actor })).toMatchObject({ projectId: null, projectWorkspaceId: source.id }); + } + await expect(tasks.create(f.companyId, { title: "Denied legacy source", createdByAgentId: agent.id, + projectWorkspaceId: source.id, workspaceSelectionActor: actor })).rejects.toThrow(/protected/); + await expect(tasks.create(f.companyId, { title: "Denied legacy binding", createdByAgentId: agent.id, + executionWorkspaceId: shared.id, workspaceSelectionActor: actor })).rejects.toThrow(/protected/); + + await db.update(executionWorkspaces).set({ mode: "isolated_workspace" }).where(eq(executionWorkspaces.id, shared.id)); + await expect(svc.validateSelection({ ...f, actor, selection: { kind: "existing", workspaceId: shared.id } })).rejects.toThrow(/protected/); + await expect(tasks.create(f.companyId, { title: "Mislabelled isolated source", createdByAgentId: agent.id, + projectWorkspaceId: source.id, workspaceSelectionActor: actor, + executionWorkspaceSettings: { mode: "isolated_workspace", workspaceStrategy: { type: "project_primary" } } })).rejects.toThrow(/protected/); + await db.update(projects).set({ executionWorkspacePolicy: { enabled: true, defaultMode: "isolated_workspace", + workspaceStrategy: { type: "project_primary" }, authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).where(eq(projects.id, project.id)); + await expect(svc.validateSelection({ ...f, actor, selection: isolatedSelection })).rejects.toThrow(/protected/); + await db.update(projects).set({ executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).where(eq(projects.id, project.id)); + + const [organization] = await db.insert(projects).values({ companyId: f.companyId, name: "Task organization" }).returning(); + await db.update(issues).set({ projectId: organization.id }).where(eq(issues.id, f.issueId)); + const [grant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "agent", + principalId: agent.id, permissionKey: "tasks:assign_scope", scope: { projectId: organization.id, assigneeAgentId: agent.id } }).returning(); + const selectionRequest = { ...f, actor, selection: sharedSelection, expectedBindingRevision: 0, requestKey: "authorized-shared" }; + await expect(svc.selectTaskWorkspace(selectionRequest)).rejects.toThrow(/protected/); + const [otherAgent] = await db.insert(agents).values({ companyId: f.companyId, name: "Another assignee", status: "idle" }).returning(); + await db.update(issues).set({ assigneeAgentId: otherAgent.id }).where(eq(issues.id, f.issueId)); + await db.update(principalPermissionGrants).set({ scope: { projectId: project.id, assigneeAgentId: otherAgent.id } }) + .where(eq(principalPermissionGrants.id, grant.id)); + await expect(svc.selectTaskWorkspace(selectionRequest)).rejects.toThrow(/protected/); + await db.update(principalPermissionGrants).set({ scope: { projectId: project.id, assigneeAgentId: agent.id } }) + .where(eq(principalPermissionGrants.id, grant.id)); + await expect(svc.selectTaskWorkspace(selectionRequest)).resolves.toMatchObject({ kind: "scheduled" }); + const responsibleUserId = randomUUID(); + await db.insert(authUsers).values({ id: responsibleUserId, name: "Source owner", email: `${responsibleUserId}@example.test`, + createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: responsibleUserId, + status: "active", membershipRole: "member" }); + const delegated = { ...actor, onBehalfOfUserId: responsibleUserId }; + await expect(svc.validateSelection({ ...f, actor: delegated, selection: sharedSelection })).rejects.toThrow(/Responsible user.*not authorized/); + const [userGrant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user", + principalId: responsibleUserId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: agent.id } }).returning(); + await expect(svc.validateSelection({ ...f, actor: delegated, selection: sharedSelection })).resolves.toMatchObject({ projectWorkspaceId: source.id }); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, userGrant.id)); + await expect(svc.validateSelection({ ...f, actor: delegated, selection: sharedSelection })).rejects.toThrow(/Responsible user.*not authorized/); + expect(await tasks.create(f.companyId, { title: "Authorized shared source", createdByAgentId: agent.id, + workspaceSelection: sharedSelection, workspaceSelectionActor: actor })).toMatchObject({ projectId: null, projectWorkspaceId: source.id }); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + await expect(svc.applyPendingTaskWorkspaceSelection({ ...f, actor, runId: randomUUID() })).rejects.toThrow(/protected/); + await expect(assertTaskWorkspaceAccess(db, actor, f.companyId, shared.id)).resolves.toBeUndefined(); + await expect(assertTaskWorkspaceAccess(db, actor, f.companyId, shared.id, { write: true })).rejects.toThrow(/protected/); + await expect(assertTaskWorkspaceAccess(db, actor, f.companyId, isolated.id, { write: true })).resolves.toBeUndefined(); + expect((await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).workspace).toBeNull(); + } finally { await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); } + }); + + it.each(["binding", "configured_source"] as const)("supersedes an older queued selection when an ordinary update changes %s", async kind => { + const f = await fixture(), svc = executionWorkspaceService(db), tasks = issueService(db); + const settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const request = { ...f, selection: { kind: "task_directory" as const }, expectedBindingRevision: 1, requestKey: "older-choice" }; + await svc.selectTaskWorkspace(request); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "New source" }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "New files", cwd: `/tmp/new-source-${f.issueId}` }).returning(); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: null, + mode: "shared_workspace", strategyType: "project_primary", name: "New binding", cwd: `/tmp/new-binding-${f.issueId}` }).returning(); + const patch = kind === "binding" ? { executionWorkspaceId: workspace.id } : { executionWorkspaceId: null, projectWorkspaceId: source.id }; + await tasks.update(f.issueId, patch); + expect(await svc.applyPendingTaskWorkspaceSelection({ ...f, runId: randomUUID() })).toBe(false); + const view = await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor); + expect(view).toMatchObject({ bindingRevision: 3, pendingSelection: null, selection: null }); + expect(view.workspace?.id ?? null).toBe(kind === "binding" ? workspace.id : null); + const [task] = await db.select().from(issues).where(eq(issues.id, f.issueId)); + expect(task.projectWorkspaceId).toBe(kind === "configured_source" ? source.id : null); + await expect(svc.selectTaskWorkspace(request)).rejects.toThrow("binding changed"); + await tasks.update(f.issueId, { projectId: project.id, ...patch }); + expect((await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).bindingRevision).toBe(3); + } finally { await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); } + }); + + it("rejects an ordinary source annotation on bound files and preserves active run ownership", async () => { + const f = await fixture(), svc = executionWorkspaceService(db), tasks = issueService(db); + await svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Other source" }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Other files", cwd: `/tmp/other-${f.issueId}` }).returning(); + await expect(tasks.update(f.issueId, { projectWorkspaceId: source.id })).rejects.toThrow("already has bound files"); + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Running worker", adapterType: "process", status: "active" }).returning(); + await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: agent.id, status: "running", invocationSource: "on_demand", nativeIssueId: f.issueId, contextSnapshot: { issueId: f.issueId } }); + const settings = instanceSettingsService(db), previous = await settings.getExperimental(); + await settings.updateExperimental({ enableIsolatedWorkspaces: true }); + try { + await expect(tasks.update(f.issueId, { executionWorkspaceId: null, projectWorkspaceId: source.id })).rejects.toThrow("still owns its workspace"); + expect(await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).toMatchObject({ bindingRevision: 1, workspace: { id: f.workspaceId } }); + await expect(svc.selectTaskWorkspace({ ...f, selection: { kind: "configured_source", projectWorkspaceId: source.id, mode: "shared" }, + expectedBindingRevision: 1, requestKey: "next-admission" })).resolves.toMatchObject({ kind: "scheduled" }); + } finally { await settings.updateExperimental({ enableIsolatedWorkspaces: previous.enableIsolatedWorkspaces }); } + }); + + it("defaults app and chat tasks to independent directories without hydrating legacy agent files", async () => { + const f = await fixture(); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "task-workspace-default-")); + vi.stubEnv("PAPERCLIP_HOME", root); + try { + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "File worker", adapterType: "process", status: "idle" }).returning(); + const legacy = resolveDefaultAgentWorkspaceDir(agent!.id); + await fs.mkdir(legacy, { recursive: true }); + const old = await fs.open(path.join(legacy, "unrelated.bin"), "w"); + await old.truncate(1_200_000_000); await old.close(); + const resolver = createHeartbeatWorkspaceResolver(db); + const first = await resolver.resolveWorkspaceForRun(agent!, { issueId: f.issueId }, null); + expect(first.cwd).not.toBe(legacy); + expect(await fs.readdir(first.cwd)).toEqual([]); + await fs.writeFile(path.join(first.cwd, "report.txt"), "task output"); + const again = await resolver.resolveWorkspaceForRun(agent!, { issueId: f.issueId }, null); + expect(again.cwd).toBe(first.cwd); + const chatId = randomUUID(); + await db.insert(issues).values({ id: chatId, companyId: f.companyId, title: "Chat report", originKind: "chat_channel" }); + const chat = await resolver.resolveWorkspaceForRun(agent!, { issueId: chatId }, null); + expect(chat.cwd).not.toBe(first.cwd); + expect(path.dirname(chat.cwd)).toBe(path.dirname(first.cwd)); + const organizationId = randomUUID(); + await db.insert(projects).values({ id: organizationId, companyId: f.companyId, name: "Organization only" }); + await db.update(issues).set({ projectId: organizationId }).where(eq(issues.id, chatId)); + const organized = await resolver.resolveWorkspaceForRun(agent!, { issueId: chatId }, null); + expect(organized.cwd).toBe(chat.cwd); + expect(organized.projectId).toBeNull(); + const nativeChatScope = await findNativeChatWorkspaceScope(db, { + adapterType: "paperclip_runner", environmentDriver: "local", companyId: f.companyId, + agentId: agent!.id, issueId: chatId, instanceRoot: root, + }); + expect(nativeChatScope?.projectId).toBeNull(); + expect((await fs.stat(path.join(legacy, "unrelated.bin"))).size).toBe(1_200_000_000); + } finally { vi.unstubAllEnvs(); await fs.rm(root, { recursive: true, force: true }); } + }); + + it.each(["missing_path", "clone_failure", "deleted_source"] as const)("refuses sibling and session fallbacks for an explicit configured source: %s", async (failure) => { + const f = await fixture(); + const root = await fs.mkdtemp(path.join(os.tmpdir(), "task-source-selection-")); + vi.stubEnv("PAPERCLIP_HOME", root); + try { + const projectId = randomUUID(), sourceId = randomUUID(), siblingId = randomUUID(); + const siblingCwd = path.join(root, "usable-sibling"); + await fs.mkdir(siblingCwd); + await fs.writeFile(path.join(siblingCwd, "source.txt"), "Other repository"); + await db.insert(projects).values({ id: projectId, companyId: f.companyId, name: "Multiple sources" }); + await db.insert(projectWorkspaces).values([ + { id: sourceId, companyId: f.companyId, projectId, name: "Selected", isPrimary: true, + sourceType: failure === "clone_failure" ? "git_repo" : "local_path", + cwd: failure === "clone_failure" ? null : path.join(root, "missing-source"), + repoUrl: failure === "clone_failure" ? path.join(root, "missing-remote.git") : null }, + { id: siblingId, companyId: f.companyId, projectId, name: "Other", sourceType: "local_path", cwd: siblingCwd }, + ]); + await db.update(issues).set({ projectId, projectWorkspaceId: sourceId, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: sourceId, mode: "shared" } }, + }).where(eq(issues.id, f.issueId)); + if (failure === "deleted_source") await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, sourceId)); + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Worker", adapterType: "process", + adapterConfig: { cwd: siblingCwd } }).returning(); + await expect(createHeartbeatWorkspaceResolver(db).resolveWorkspaceForRun(agent!, { issueId: f.issueId }, { cwd: siblingCwd })) + .rejects.toMatchObject({ code: "workspace_validation_failed", resultJson: { workspaceValidation: { + reason: failure === "clone_failure" ? "git_worktree_base_materialization_failed" : "configured_workspace_unavailable", + issueProjectWorkspaceId: sourceId, baseCwdFallback: false, + materializationFailures: failure === "clone_failure" ? [expect.objectContaining({ projectWorkspaceId: sourceId })] : [], + } } }); + expect(await fs.readFile(path.join(siblingCwd, "source.txt"), "utf8")).toBe("Other repository"); + expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0].executionWorkspaceId).toBeNull(); + if (failure === "missing_path") { + await fs.mkdir(path.join(root, "missing-source")); + const repaired = await createHeartbeatWorkspaceResolver(db).resolveWorkspaceForRun(agent!, { issueId: f.issueId }, { cwd: siblingCwd }); + expect(repaired).toMatchObject({ workspaceId: sourceId, cwd: path.join(root, "missing-source"), baseCwdFallback: false }); + } + } finally { vi.unstubAllEnvs(); await fs.rm(root, { recursive: true, force: true }); } + }); + + it("retains every task privacy source when simultaneous tasks bind the same folder", async () => { + const f = await fixture(), secondIssueId = randomUUID(), svc = executionWorkspaceService(db); + await db.insert(issues).values({ id: secondIssueId, companyId: f.companyId, title: "Second task" }); + await Promise.all([svc.bindTaskWorkspace(f.companyId, f.issueId, f.workspaceId), svc.bindTaskWorkspace(f.companyId, secondIssueId, f.workspaceId)]); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)); + expect(workspace?.metadata).toMatchObject({ _issuePrivacySources: { [f.issueId]: true, [secondIssueId]: true } }); + }); + + it.each(["creation", "selection"] as const)("retains private writers after an existing workspace is installed by %s and then detached", async (installation) => { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + try { + const f = await fixture(), svc = executionWorkspaceService(db); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Organization" }).returning(); + const [reader] = await db.insert(agents).values({ companyId: f.companyId, name: "Unrelated reader", adapterType: "process" }).returning(); + const writer = await issueService(db).create(f.companyId, { + title: "Private writer", projectId: project.id, visibility: "private", responsibleUserId: "private-owner", + workspaceSelectionActor: f.actor, + ...(installation === "creation" ? { workspaceSelection: { kind: "existing" as const, workspaceId: f.workspaceId } } : {}), + }); + if (installation === "selection") { + await svc.selectTaskWorkspace({ ...f, issueId: writer.id, selection: { kind: "existing", workspaceId: f.workspaceId }, + expectedBindingRevision: 0, requestKey: "install-existing" }); + await svc.applyPendingTaskWorkspaceSelection({ ...f, issueId: writer.id, runId: randomUUID() }); + } + const installed = await svc.inspectTaskWorkspace(f.companyId, writer.id, f.actor); + expect(installed.workspace?.id).toBe(f.workspaceId); + expect(installed.workspace?.metadata?._issuePrivacySources).toMatchObject({ [writer.id]: true }); + await svc.selectTaskWorkspace({ ...f, issueId: writer.id, selection: { kind: "task_directory" }, + expectedBindingRevision: installed.bindingRevision, requestKey: "detach-private-writer" }); + await svc.applyPendingTaskWorkspaceSelection({ ...f, issueId: writer.id, runId: randomUUID() }); + const [retained] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)); + expect(retained.metadata?._issuePrivacySources).toMatchObject({ [writer.id]: true }); + expect(await canActorReadExecutionWorkspace(db, { type: "agent", agentId: reader.id, companyId: f.companyId }, retained.id)).toBe(false); + } finally { vi.unstubAllEnvs(); } + }); + + it("deletes a whole company in source-workspace dependency order", async () => { + const f = await fixture(), sourceProjectId = randomUUID(); + await db.insert(projects).values({ id: sourceProjectId, companyId: f.companyId, name: "Source policy" }); + await db.update(executionWorkspaces).set({ projectId: sourceProjectId }).where(eq(executionWorkspaces.id, f.workspaceId)); + expect(await deleteCompany(db, f.companyId)).toMatchObject({ id: f.companyId }); + expect(await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId))).toEqual([]); + expect(await db.select().from(projects).where(eq(projects.id, sourceProjectId))).toEqual([]); + }); + + it("retains source-project policy until workspaces are explicitly cleaned up", async () => { + const f = await fixture(), sourceProjectId = randomUUID(); + await db.insert(projects).values({ id: sourceProjectId, companyId: f.companyId, name: "Source policy" }); + await db.update(executionWorkspaces).set({ projectId: sourceProjectId }).where(eq(executionWorkspaces.id, f.workspaceId)); + await expect(db.delete(projects).where(eq(projects.id, sourceProjectId))).rejects.toThrow(); + expect((await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, f.workspaceId)))[0]?.projectId).toBe(sourceProjectId); + }); + + it("rejects foreign-company selections before changing pending state", async () => { + const f = await fixture(), other = await fixture(), svc = executionWorkspaceService(db); + await expect(svc.selectTaskWorkspace({ ...f, expectedBindingRevision: 0, requestKey: "foreign", + selection: { kind: "existing", workspaceId: other.workspaceId } })).rejects.toThrow("unavailable or inaccessible"); + expect((await svc.inspectTaskWorkspace(f.companyId, f.issueId, f.actor)).pendingSelection).toBeNull(); + }); +}); diff --git a/server/src/__tests__/task-workspace-child-trust.test.ts b/server/src/__tests__/task-workspace-child-trust.test.ts new file mode 100644 index 00000000000..7c1dee68714 --- /dev/null +++ b/server/src/__tests__/task-workspace-child-trust.test.ts @@ -0,0 +1,81 @@ +import { randomUUID } from "node:crypto"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, authUsers, companyMemberships, createDb, executionWorkspaces, heartbeatRuns, issues, projects, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { issueService } from "../services/issues.js"; +import { createRepositoryTrustFixture } from "./helpers/repository-trust-fixture.js"; +import { getEmbeddedPostgresTestSupport } from "./helpers/embedded-postgres.js"; + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("child task workspace trust", () => { + const context = createRepositoryTrustFixture({ managedAuth: vi.fn(), materialize: vi.fn(), catalog: vi.fn() }); + let db: ReturnType; + beforeAll(async () => { ({ db } = await context.setup()); }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterAll(context.cleanup); + + async function fixture() { + const f = await context.fixture(); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Review source" }).returning(); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [project.id] } } }; + await db.update(projects).set({ executionWorkspacePolicy: policy }).where(eq(projects.id, project.id)); + await db.update(executionWorkspaces).set({ projectId: project.id }).where(eq(executionWorkspaces.id, f.workspace.id)); + await db.update(issues).set({ executionPolicy: policy }).where(eq(issues.id, f.issueId)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }).where(eq(heartbeatRuns.id, f.runId)); + const input = { title: `Child ${randomUUID()}`, parentId: f.issueId, assigneeAgentId: f.agentId, + executionPolicy: policy, createdByAgentId: f.agentId, workspaceSelectionActor: f.actor, + workspaceSelection: { kind: "existing" as const, workspaceId: f.workspace.id } }; + return { ...f, project, policy, input }; + } + + it.each(["direct", "descendant", "omitted_policy"] as const)("creates a matching low-trust child with an explicit existing workspace (%s)", async scenario => { + const f = await fixture(); + if (scenario === "descendant") { + const [parent] = await db.insert(issues).values({ companyId: f.companyId, parentId: f.issueId, + title: "Intermediate task", assigneeAgentId: f.agentId, executionPolicy: f.policy }).returning(); + f.input.parentId = parent.id; + } + const input = scenario === "omitted_policy" ? { ...f.input, executionPolicy: undefined } : f.input; + await expect(issueService(db).create(f.companyId, input)).resolves.toMatchObject({ + parentId: f.input.parentId, executionWorkspaceId: f.workspace.id, + }); + }); + it.each(["child_policy", "unrelated_parent", "missing_parent", "missing_run", "cross_company_run", "sponsor", "live_parent_policy", "absent_saved_policy", "child_agent_policy"] as const)( + "rejects incompatible child creation (%s)", async scenario => { + const f = await fixture(); + const different = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, rootIssueId: randomUUID() } } }; + if (scenario === "child_policy") f.input.executionPolicy = different; + if (scenario === "unrelated_parent") { + const [parent] = await db.insert(issues).values({ companyId: f.companyId, title: "Other root" }).returning(); + f.input.parentId = parent.id; + } + if (scenario === "missing_parent") f.input.parentId = randomUUID(); + if (scenario === "missing_run") f.input.workspaceSelectionActor = { ...f.actor, runId: randomUUID() }; + if (scenario === "cross_company_run") f.input.workspaceSelectionActor = { ...f.actor, runId: (await fixture()).runId }; + if (scenario === "sponsor") { + const id = randomUUID(); + await db.insert(authUsers).values({ id, name: "Other operator", email: `${id}@example.test`, emailVerified: true, createdAt: new Date(), updatedAt: new Date() }); + await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: id, membershipRole: "operator", status: "active" }); + f.input.workspaceSelectionActor = { ...f.actor, onBehalfOfUserId: id }; + } + if (scenario === "live_parent_policy") await db.update(issues).set({ executionPolicy: different }).where(eq(issues.id, f.issueId)); + if (scenario === "absent_saved_policy") await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId } }).where(eq(heartbeatRuns.id, f.runId)); + if (scenario === "child_agent_policy") { + const [agent] = await db.insert(agents).values({ companyId: f.companyId, name: "Other boundary", adapterType: "process", permissions: different }).returning(); + f.input.assigneeAgentId = agent.id; + } + const before = await db.select().from(issues).where(eq(issues.companyId, f.companyId)); + await expect(issueService(db).create(f.companyId, f.input)).rejects.toThrow(); + expect(await db.select().from(issues).where(eq(issues.companyId, f.companyId))).toEqual(before); + }, + ); + + it("does not infer child creation from an arbitrary issue-less workspace selection", async () => { + const f = await fixture(); + await expect(executionWorkspaceService(db).validateSelection({ companyId: f.companyId, actor: f.actor, + selection: f.input.workspaceSelection, parentIssueId: f.issueId, executionPolicy: f.policy, + assigneeAgentId: f.agentId })).rejects.toThrow(/Physical workspace source trust/); + }); +}); diff --git a/server/src/__tests__/task-workspace-repositories-persistence.test.ts b/server/src/__tests__/task-workspace-repositories-persistence.test.ts new file mode 100644 index 00000000000..ebe69894d70 --- /dev/null +++ b/server/src/__tests__/task-workspace-repositories-persistence.test.ts @@ -0,0 +1,247 @@ +import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { randomUUID } from "node:crypto"; +import { mkdtemp, rm, mkdir, writeFile, readFile, realpath, open, lstat, symlink, readdir } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { constants as fsConstants } from "node:fs"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { and, eq } from "drizzle-orm"; +import { agents, companies, createDb, executionWorkspaces, executionWorkspaceRepositories, heartbeatRuns, issues } from "@paperclipai/db"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { executionWorkspaceRepositoryService } from "../services/execution-workspace-repositories.js"; +import { listLocalProjectRepositories } from "@paperclipai/adapter-utils/ssh"; +import { readManagedWorkspaceRepositories, disposeGitWorkspaceSnapshot } from "@paperclipai/adapter-utils/git-workspace-sync"; +import { captureDirectorySnapshot, disposeDirectorySnapshot } from "@paperclipai/adapter-utils/workspace-restore-merge"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; + +const catalog = vi.hoisted(() => ({ available: true })); +vi.mock("../services/tool-access.js", () => ({ toolAccessService: () => ({ listProjectRepositories: async () => ({ repositories: catalog.available ? [{ id: "123", fullName: "team/source", url: "https://github.com/team/source", connections: [] }] : [] }) }) })); +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("durable task repository receipts", () => { + let temporary: Awaited>; + let db: ReturnType; + let companyId: string, issueId: string, workspaceId: string, agentId: string, cwd: string; + const actor = { type: "board" as const, source: "local_implicit" as const, userId: "local-board" }; + beforeAll(async () => { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + temporary = await startEmbeddedPostgresTestDatabase("paperclip-task-repositories-"); + db = createDb(temporary.connectionString); + cwd = await mkdtemp(path.join(tmpdir(), "paperclip-task-repositories-files-")); + [companyId] = (await db.insert(companies).values({ name: "Task repositories", issuePrefix: "TREPO" }).returning()).map(row => row.id); + [agentId] = (await db.insert(agents).values({ companyId, name: "Repository worker", adapterType: "process", status: "idle" }).returning()).map(row => row.id); + [workspaceId] = (await db.insert(executionWorkspaces).values({ companyId, projectId: null, name: "Task files", cwd, mode: "shared_workspace", strategyType: "task_directory" }).returning()).map(row => row.id); + [issueId] = (await db.insert(issues).values({ companyId, title: "Projectless task", executionWorkspaceId: workspaceId }).returning()).map(row => row.id); + }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterAll(async () => { vi.unstubAllEnvs(); await temporary?.cleanup(); if (cwd) await rm(cwd, { recursive: true, force: true }); }); + const prepareForAdmission = async (input: Parameters["prepareForAdmission"]>[0]) => { + await db.insert(heartbeatRuns).values({ id: input.runId, companyId: input.companyId, agentId: input.agentId, + status: "running", responsibleUserId: input.responsibleUserId, contextSnapshot: { issueId: input.issueId } }).onConflictDoNothing(); + return executionWorkspaceRepositoryService(db).prepareForAdmission(input); + }; + const request = (requestKey: string, ref?: string) => executionWorkspaceRepositoryService(db).request({ companyId, issueId, actor, request: { repository: { kind: "catalog", id: "123" }, requestKey, ...(ref ? { ref } : {}) } }); + it("publishes a requested repository from an absent path through real admission locking and cloning", async () => { + const root = await mkdtemp(path.join(tmpdir(), "paperclip-fresh-repository-")); + const previousPath = process.env.PATH; + const exec = promisify(execFileCallback); + try { + const realGit = (await exec("which", ["git"])).stdout.trim(); + const source = path.join(root, "source"), taskRoot = path.join(root, "task"), bin = path.join(root, "bin"); + await Promise.all([mkdir(source), mkdir(taskRoot), mkdir(bin)]); + await exec(realGit, ["init", "--initial-branch=main", taskRoot]); + await writeFile(path.join(taskRoot, "report.txt"), "Task-owned report\n"); + const git = async (...args: string[]) => (await exec(realGit, ["-C", source, ...args])).stdout.trim(); + await git("init", "--initial-branch=main"); + await writeFile(path.join(source, "source.txt"), "original source\n"); + await git("add", "."); + await git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"); + const pin = await git("rev-parse", "HEAD"); + const cloneStarted = path.join(root, "clone-started"), cloneReleased = path.join(root, "clone-released"); + await writeFile(path.join(bin, "git"), `#!${process.execPath}\n` + + `const { spawnSync } = require('node:child_process'); const fs = require('node:fs'); const args = process.argv.slice(2);\n` + + `const remote = args.includes('clone') ? args.at(-2) : null;\n` + + `if (remote && remote !== 'https://github.com/team/source') process.exit(97);\n` + + `if (remote) { fs.writeFileSync(${JSON.stringify(cloneStarted)}, 'started'); const deadline = Date.now() + 10000; while (!fs.existsSync(${JSON.stringify(cloneReleased)})) { if (Date.now() > deadline) process.exit(96); Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20); } }\n` + + `if (remote) args[args.length - 2] = ${JSON.stringify(source)};\n` + + `const result = spawnSync(${JSON.stringify(realGit)}, args, { stdio: 'inherit' });\n` + + `if (result.status) process.exit(result.status);\n` + + `if (remote) process.exit(spawnSync(${JSON.stringify(realGit)}, ['-C', args.at(-1), 'remote', 'set-url', 'origin', remote], { stdio: 'inherit' }).status ?? 98);\n` + + `process.exit(result.status ?? 99);\n`, { mode: 0o755 }); + process.env.PATH = `${bin}${path.delimiter}${previousPath ?? ""}`; + const [workspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Fresh task root", cwd: taskRoot, + mode: "shared_workspace", strategyType: "task_directory" }).returning(); + const [task] = await db.insert(issues).values({ companyId, title: "Fresh repository", executionWorkspaceId: workspace.id }).returning(); + const intent = await executionWorkspaceRepositoryService(db).request({ companyId, issueId: task.id, actor, + request: { repository: { kind: "catalog", id: "123" }, requestKey: "fresh-repository" } }); + const checkout = path.join(taskRoot, intent.repository.relativePath); + expect(await lstat(checkout).catch(() => null)).toBeNull(); + const admission = { companyId, issueId: task.id, workspaceId: workspace.id, cwd: taskRoot, + agentId, runId: randomUUID(), responsibleUserId: "local-board" }; + const [readyWorkspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Shared physical root", cwd: taskRoot, + mode: "shared_workspace", strategyType: "operator_directory" }).returning(); + const [readyTask] = await db.insert(issues).values({ companyId, title: "Reuse another repository", executionWorkspaceId: readyWorkspace.id }).returning(); + const readyIntent = await executionWorkspaceRepositoryService(db).request({ companyId, issueId: readyTask.id, actor, + request: { repository: { kind: "url", url: "https://github.com/team/ready" }, requestKey: "ready-repository" } }); + const readyCheckout = path.join(taskRoot, readyIntent.repository.relativePath); + await mkdir(path.dirname(readyCheckout), { recursive: true }); + await exec(realGit, ["clone", "--no-hardlinks", source, readyCheckout]); + await exec(realGit, ["-C", readyCheckout, "remote", "set-url", "origin", readyIntent.repository.repoUrl]); + await db.update(executionWorkspaceRepositories).set({ state: "ready", pinnedCommit: pin }) + .where(eq(executionWorkspaceRepositories.id, readyIntent.operationId)); + const pending = Promise.all([1, 2].map(() => prepareForAdmission(admission))); + let results: Awaited; + let timeout: ReturnType | undefined; + try { + await vi.waitFor(async () => expect(await lstat(cloneStarted).catch(() => null)).not.toBeNull()); + const ready = await Promise.race([ + prepareForAdmission({ ...admission, workspaceId: readyWorkspace.id, issueId: readyTask.id, runId: randomUUID() }), + new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error("Unrelated repository reuse waited for a blocked clone")), 2000); }), + ]); + expect(ready[0]).toMatchObject({ id: readyIntent.operationId, pinnedCommit: pin }); + expect(await listLocalProjectRepositories(taskRoot)).toEqual([readyIntent.repository.relativePath]); + const snapshots = await readManagedWorkspaceRepositories(taskRoot); + try { expect(snapshots.map(repo => repo.path)).toEqual([readyIntent.repository.relativePath]); } + finally { await Promise.all(snapshots.map(repo => disposeGitWorkspaceSnapshot(repo.snapshot))); } + expect((await readdir(path.join(taskRoot, ".paperclip-runtime", "repository-staging"))).filter(name => name.includes(".clone-")).length).toBe(1); + expect((await exec(realGit, ["-C", taskRoot, "status", "--porcelain", "--untracked-files=all"])).stdout.trim()).toBe("?? report.txt"); + await exec(realGit, ["-C", taskRoot, "add", "."]); + expect((await exec(realGit, ["-C", taskRoot, "diff", "--cached", "--name-only"])).stdout.trim()).toBe("report.txt"); + const baseline = await captureDirectorySnapshot(taskRoot, { exclude: [".paperclip-runtime"] }); + try { expect([...baseline.entries.keys()].some(entry => entry.includes("repository-staging") || entry.includes(".clone-"))).toBe(false); } + finally { await disposeDirectorySnapshot(baseline); } + } finally { + clearTimeout(timeout); + await writeFile(cloneReleased, "released"); + results = await pending; + } + for (const prepared of results) expect(prepared).toEqual([expect.objectContaining({ id: intent.operationId, pinnedCommit: pin })]); + expect(await readFile(path.join(checkout, "source.txt"), "utf8")).toBe("original source\n"); + expect(JSON.parse(await readFile(path.join(checkout, ".git", "paperclip-workspace-owner.json"), "utf8"))) + .toMatchObject({ repositoryId: intent.operationId, pinnedCommit: pin }); + expect((await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.id, intent.operationId)))[0]) + .toMatchObject({ state: "ready", pinnedCommit: pin, failureCode: null }); + await writeFile(path.join(checkout, "source.txt"), "retained dirty work\n"); + await prepareForAdmission(admission); + expect(await readFile(path.join(checkout, "source.txt"), "utf8")).toBe("retained dirty work\n"); + } finally { + if (previousPath === undefined) delete process.env.PATH; else process.env.PATH = previousPath; + await rm(root, { recursive: true, force: true }); + } + }); + it.skipIf(process.platform === "win32").each(["parent", "ignore_file"])("rejects a symlinked runtime staging %s before cloning", async (unsafeEntry) => { + const root = await mkdtemp(path.join(tmpdir(), "paperclip-unsafe-repository-staging-")); + try { + const taskRoot = path.join(root, "task"), outside = path.join(root, "outside"); + await mkdir(taskRoot); await mkdir(outside); + if (unsafeEntry === "parent") await symlink(outside, path.join(taskRoot, ".paperclip-runtime")); + else { + const staging = path.join(taskRoot, ".paperclip-runtime", "repository-staging"); + await mkdir(staging, { recursive: true }); + await symlink(outside, path.join(staging, ".gitignore")); + } + const [workspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Unsafe staging", cwd: taskRoot, + mode: "shared_workspace", strategyType: "task_directory" }).returning(); + const [task] = await db.insert(issues).values({ companyId, title: "Unsafe staging", executionWorkspaceId: workspace.id }).returning(); + await executionWorkspaceRepositoryService(db).request({ companyId, issueId: task.id, actor, + request: { repository: { kind: "catalog", id: "123" }, requestKey: "unsafe-staging" } }); + await expect(prepareForAdmission({ companyId, issueId: task.id, + workspaceId: workspace.id, cwd: taskRoot, agentId, runId: randomUUID(), responsibleUserId: "local-board" })) + .rejects.toThrow(unsafeEntry === "parent" ? "staging directory escapes" : "ignore file is not a regular file"); + expect(await readdir(outside)).toEqual([]); + } finally { await rm(root, { recursive: true, force: true }); } + }); + it("converges concurrent requests and persists each retry key without creating a project", async () => { + const [peerIssue] = await db.insert(issues).values({ companyId, title: "Peer task sharing the root", executionWorkspaceId: workspaceId }).returning(); + await db.update(executionWorkspaces).set({ sourceIssueId: issueId }).where(eq(executionWorkspaces.id, workspaceId)); + const [first, second] = await Promise.all([ + request("one"), + executionWorkspaceRepositoryService(db).request({ companyId, issueId: peerIssue.id, actor, request: { repository: { kind: "catalog", id: "123" }, requestKey: "two" } }), + ]); + expect(first.operationId).toBe(second.operationId); + expect(first).toMatchObject({ kind: "requires_next_admission", applies: "next_normal_admission" }); + const rows = await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.executionWorkspaceId, workspaceId)); + expect(rows).toHaveLength(1); + expect(rows[0].requestKeys.sort()).toEqual(["one", "two"]); + expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0]).toMatchObject({ projectId: null, executionWorkspaceId: workspaceId }); + await expect(request("two", "different-ref")).rejects.toThrow(/conflicts/); + await expect(executionWorkspaceRepositoryService(db).request({ companyId, issueId, actor, request: { repository: { kind: "url", url: "https://github.com/other/source" }, requestKey: "two" } })).rejects.toThrow(/conflicts/); + }); + it("rejects cross-company task IDs and revoked catalog permission even with a saved receipt", async () => { + const [other] = await db.insert(companies).values({ name: "Foreign", issuePrefix: "FOREIGN" }).returning(); + await expect(executionWorkspaceRepositoryService(db).request({ companyId: other.id, issueId, actor, request: { repository: { kind: "catalog", id: "123" }, requestKey: "foreign" } })).rejects.toThrow(/not found/); + catalog.available = false; + try { + await expect(request("one")).rejects.toThrow(/no longer available/); + await expect(prepareForAdmission({ companyId, issueId, workspaceId, cwd, agentId, runId: randomUUID(), responsibleUserId: "local-board" })).rejects.toThrow(/no longer available/); + } finally { catalog.available = true; } + }); + it("retains durable ownership when Git bundle restoration omits the temporary receipt", async () => { + const [inventory] = await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.executionWorkspaceId, workspaceId)); + const checkout = path.join(cwd, inventory.relativePath); + await mkdir(checkout, { recursive: true }); + const git = async (...args: string[]) => (await promisify(execFileCallback)("git", ["-C", checkout, ...args])).stdout.trim(); + await git("init", "--initial-branch=main"); + await git("remote", "add", "origin", inventory.repoUrl); + await writeFile(path.join(checkout, "source.txt"), "restored repository\n"); + await git("add", "."); + await git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "restored"); + const commit = await git("rev-parse", "HEAD"); + await db.update(executionWorkspaceRepositories).set({ pinnedCommit: commit, state: "ready" }).where(eq(executionWorkspaceRepositories.id, inventory.id)); + await writeFile(path.join(checkout, "source.txt"), "uncommitted work\n"); + const prepared = await prepareForAdmission({ companyId, issueId, workspaceId, cwd, agentId, runId: randomUUID(), responsibleUserId: "local-board" }); + expect(prepared).toHaveLength(1); + expect(prepared[0].pinnedCommit).toBe(commit); + expect(await readFile(path.join(checkout, "source.txt"), "utf8")).toBe("uncommitted work\n"); + if (process.platform !== "win32") { + const receiptPath = path.join(checkout, ".git", "paperclip-workspace-owner.json"); + await promisify(execFileCallback)("mkfifo", [receiptPath]); + let deadline: ReturnType | undefined; + try { + await Promise.race([ + prepareForAdmission({ companyId, issueId, workspaceId, cwd, agentId, runId: randomUUID(), responsibleUserId: "local-board" }), + new Promise((_, reject) => { deadline = setTimeout(() => reject(new Error("FIFO receipt blocked admission")), 3000); }), + ]); + } finally { + clearTimeout(deadline); + const writer = await open(receiptPath, fsConstants.O_WRONLY | fsConstants.O_NONBLOCK).catch(() => null); + await writer?.close(); + await rm(receiptPath, { force: true }); + } + } + }); + it("reuses the same physical checkout across logical workspaces and rechecks retained privacy", async () => { + const [secondAgent] = await db.insert(agents).values({ companyId, name: "Second agent", adapterType: "process" }).returning(); + const [secondWorkspace] = await db.insert(executionWorkspaces).values({ companyId, projectId: null, name: "Same configured folder", cwd, mode: "shared_workspace", strategyType: "operator_directory" }).returning(); + const [secondIssue] = await db.insert(issues).values({ companyId, title: "Second task", assigneeAgentId: secondAgent.id, executionWorkspaceId: secondWorkspace.id }).returning(); + const service = executionWorkspaceRepositoryService(db); + const intent = await service.request({ companyId, issueId: secondIssue.id, actor, request: { repository: { kind: "catalog", id: "123" }, requestKey: "shared-second" } }); + const admission = { companyId, issueId: secondIssue.id, workspaceId: secondWorkspace.id, cwd, agentId: secondAgent.id, runId: randomUUID(), responsibleUserId: null }; + const [original] = await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.executionWorkspaceId, workspaceId)); + const prepared = await prepareForAdmission(admission); + expect(prepared[0]).toMatchObject({ id: intent.operationId, cwd: path.join(await realpath(cwd), original.relativePath), pinnedCommit: original.pinnedCommit }); + expect(await readFile(path.join(prepared[0].cwd, "source.txt"), "utf8")).toBe("uncommitted work\n"); + expect(await readFile(path.join(prepared[0].cwd, ".git", "paperclip-workspace-owner.json"), "utf8").catch(() => null)).toBeNull(); + const [differentRefWorkspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Conflicting ref", cwd, mode: "shared_workspace", strategyType: "operator_directory" }).returning(); + const [differentRefIssue] = await db.insert(issues).values({ companyId, title: "Different ref", executionWorkspaceId: differentRefWorkspace.id }).returning(); + await service.request({ companyId, issueId: differentRefIssue.id, actor, request: { repository: { kind: "catalog", id: "123" }, ref: "release", requestKey: "conflicting-ref" } }); + await expect(prepareForAdmission({ ...admission, issueId: differentRefIssue.id, workspaceId: differentRefWorkspace.id, runId: randomUUID() })).rejects.toThrow(/different ref/); + const [privateSource] = await db.insert(issues).values({ companyId, title: "Retained private source", visibility: "private" }).returning(); + const owner = executionWorkspaceService(db); + await owner.bindTaskWorkspace(companyId, privateSource.id, workspaceId); + const [movedWorkspace] = await db.insert(executionWorkspaces).values({ companyId, name: "Private task moved away", cwd: path.join(cwd, "moved-private-task"), mode: "shared_workspace", strategyType: "task_directory" }).returning(); + await owner.bindTaskWorkspace(companyId, privateSource.id, movedWorkspace.id); + const [retainedWorkspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, workspaceId)); + expect(retainedWorkspace.metadata?._issuePrivacySources).toMatchObject({ [privateSource.id]: true }); + await expect(prepareForAdmission(admission)).rejects.toThrow(/Task workspace access is no longer available/); + expect(await readFile(path.join(prepared[0].cwd, "source.txt"), "utf8")).toBe("uncommitted work\n"); + }); + it("stages a root change without replacing active files or adding a wake", async () => { + const owner = executionWorkspaceService(db); + await expect(owner.selectTaskWorkspace({ companyId, issueId, actor, selection: { kind: "task_directory" }, expectedBindingRevision: 0, requestKey: "move" })).resolves.toMatchObject({ kind: "scheduled", applies: "next_normal_admission" }); + const [issue] = await db.select().from(issues).where(and(eq(issues.companyId, companyId), eq(issues.id, issueId))); + expect(issue.executionWorkspaceId).toBe(workspaceId); + expect(issue.workspacePendingSelection?.requestKey).toBe("move"); + expect(issue.workspaceBindingRevision).toBe(1); + }); +}); diff --git a/server/src/__tests__/task-workspace-repositories-trust.test.ts b/server/src/__tests__/task-workspace-repositories-trust.test.ts new file mode 100644 index 00000000000..1cd652d13a2 --- /dev/null +++ b/server/src/__tests__/task-workspace-repositories-trust.test.ts @@ -0,0 +1,853 @@ +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { createHash, randomUUID } from "node:crypto"; +import { mkdtemp, mkdir, writeFile, readFile, realpath, rm, symlink } from "node:fs/promises"; +import fs from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, authUsers, companies, companyMemberships, createDb, executionWorkspaceRepositoryRoots, executionWorkspaceRepositoryAccess, executionWorkspaceRepositories, executionWorkspaces, + heartbeatRuns, issueAccessGrants, issues, projects, projectWorkspaces, projectAccessMembers, principalPermissionGrants, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { assertExecutionWorkspaceRootReadAccess, assertExecutionWorkspaceRootWriteAccess, assertIsolatedWorkspaceSourceReadAccess, retainIsolatedWorkspaceSourceAccess, executionWorkspaceRepositoryService } from "../services/execution-workspace-repositories.js"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { projectService } from "../services/projects.js"; +import { buildNativeExecutionInput } from "../services/native-runtime/native-execution-input.js"; +import { nativeRuntimeContextFixture } from "../services/native-runtime/runtime-context.test-fixture.js"; +import { buildGitAuthInvocation } from "../services/git-credentials.js"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; + +import { createRepositoryTrustFixture } from "./helpers/repository-trust-fixture.js"; + +const acquisition = vi.hoisted(() => ({ managedAuth: vi.fn(), materialize: vi.fn(), catalog: vi.fn() })); +vi.mock("../services/tool-access.js", () => ({ toolAccessService: () => ({ listProjectRepositories: acquisition.catalog }) })); +vi.mock("../services/git-credentials.js", async (original) => ({ + ...await original(), + createGitRemoteAuthProvider: () => acquisition.managedAuth, +})); +vi.mock("../services/managed-repository-checkout.js", async (original) => ({ + ...await original(), + materializeManagedProjectWorkspace: acquisition.materialize, +})); + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("task repository trust authority", () => { + const context = createRepositoryTrustFixture(acquisition); + let db: ReturnType; + let root: string; + beforeAll(async () => { ({ db, root } = await context.setup()); }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); }); + afterAll(context.cleanup); + const { fixture, lowTrustPeer, standardPeer, privateTask, grantPeer, localPrivateClone } = context; + + it.each(["empty", "different_identity", "unreadable_alias", "loop_alias", "anonymous_private_edits", "authorized_peer"] as const)( + "authorizes every physical repository source for a standard reader with %s", async scenario => { + const f = await fixture(), peer = await standardPeer(f); + if (scenario === "different_identity") await f.service.request({ companyId: f.companyId, issueId: peer.issueId, actor: peer.actor, + request: { repository: { kind: "url", url: "https://github.com/public/other" }, requestKey: randomUUID() } }); + await privateTask(f); + const intent = await f.request(scenario === "anonymous_private_edits" ? { kind: "url", url: "https://github.com/public/source" } : { kind: "catalog", id: "private-repo" }); + await localPrivateClone(f); + const [prepared] = await f.service.prepareForAdmission(f.admission); + expect(await readFile(path.join(prepared.cwd, "private.txt"), "utf8")).toBe("Private task edits\n"); + if (scenario === "unreadable_alias" || scenario === "loop_alias") { + const alias = path.join(root, `${f.issueId}-private-alias`); + await symlink(scenario === "loop_alias" ? alias : f.admission.cwd, alias); + await db.update(executionWorkspaces).set({ cwd: alias }).where(eq(executionWorkspaces.id, f.workspace.id)); + await db.delete(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + const actual = fs.realpath.bind(fs); + vi.spyOn(fs, "realpath").mockImplementation(async pathname => { + if (scenario === "unreadable_alias" && String(pathname) === alias) throw Object.assign(new Error("Private alias unavailable"), { code: "EACCES" }); + return actual(pathname); + }); + } + if (scenario === "authorized_peer") { + await grantPeer(f, peer); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + } else await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary/); + expect((await f.service.list(f.companyId, f.workspace.id))[0].id).toBe(intent.operationId); + expect(acquisition.materialize).toHaveBeenCalledOnce(); + }, + ); + + it.each(["retained", "legacy", "known_origin_removed"] as const)("keeps independent copies of the same repository separate (%s provenance)", async provenance => { + const f = await fixture(), peer = await standardPeer(f); + const peerCwd = path.join(root, peer.issueId); + await mkdir(peerCwd); + await db.update(executionWorkspaces).set({ cwd: peerCwd }).where(eq(executionWorkspaces.id, peer.workspace.id)); + peer.admission.cwd = peerCwd; + await privateTask(f); + const repository = { kind: "url" as const, url: "https://github.com/public/source" }; + await f.request(repository); + await localPrivateClone(f); + const [privateCheckout] = await f.service.prepareForAdmission(f.admission); + await f.service.request({ companyId: f.companyId, issueId: peer.issueId, actor: peer.actor, + request: { repository, requestKey: randomUUID() } }); + await localPrivateClone({ ...f, issueId: peer.issueId }); + const [peerCheckout] = await f.service.prepareForAdmission(peer.admission); + expect(peerCheckout.cwd).not.toBe(privateCheckout.cwd); + expect(await readFile(path.join(peerCheckout.cwd, "private.txt"), "utf8")).toBe("Private task edits\n"); + if (provenance === "legacy") await db.delete(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + if (provenance === "known_origin_removed") await rm(f.admission.cwd, { recursive: true }); + const actual = await vi.importActual("../services/managed-repository-checkout.js"); + acquisition.materialize.mockImplementationOnce(actual.materializeManagedProjectWorkspace); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toHaveLength(1); + const beforeRead = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: peerCwd, + sourceWorkspaceId: peer.workspace.id, actor: peer.actor })).resolves.toBeUndefined(); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: peer.actor })).rejects.toThrow(/workspace access|authorization boundary/); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId))).toEqual(beforeRead); + }); + + it.each(["update", "archive"] as const)("preserves legacy private publishers before %s moves their workspace away from shared files", async operation => { + const f = await fixture(), peer = await standardPeer(f); + const repository = { kind: "url" as const, url: "https://github.com/public/legacy-shared" }; + await f.service.request({ companyId: f.companyId, issueId: peer.issueId, actor: peer.actor, + request: { repository, requestKey: randomUUID() } }); + await privateTask(f); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private legacy source", visibility: "private" }).returning(); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + await db.update(executionWorkspaces).set({ projectId: project.id, sourceIssueId: f.issueId }).where(eq(executionWorkspaces.id, f.workspace.id)); + await f.request(repository); + await localPrivateClone(f); + const [checkout] = await f.service.prepareForAdmission(f.admission); + expect(await readFile(path.join(checkout.cwd, "private.txt"), "utf8")).toBe("Private task edits\n"); + await db.delete(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + const moved = path.join(root, `${f.issueId}-legacy-moved`); + await mkdir(moved); + const patch = { cwd: moved, projectId: null, sourceIssueId: null, metadata: {} }; + const svc = executionWorkspaceService(db); + if (operation === "archive") { + const result = await svc.archiveWorkspaceUnderLifecycleLock({ id: f.workspace.id, patch, closedAt: new Date() }); + expect(result?.outcome).toBe("archived"); + if (result?.outcome === "archived") { + expect(result.capturedGeneration).toBeGreaterThan(0); + expect(result.workspace.status).toBe("archived"); + expect(result.workspace.metadata?._issuePrivacySources).toHaveProperty(f.issueId); + } + } else await svc.update(f.workspace.id, patch); + await db.update(issues).set({ executionWorkspaceId: null }).where(eq(issues.id, f.issueId)); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: peer.admission.cwd, + sourceWorkspaceId: peer.workspace.id, actor: peer.actor })).rejects.toThrow(/workspace access|authorization boundary|source project/); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary|source project/); + const retained = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(retained.some(entry => entry.kind === "source" && entry.scopeId === f.workspace.id)).toBe(true); + expect(retained.some(entry => entry.kind === "project_source" && entry.scopeId === project.id)).toBe(true); + expect(retained.some(entry => entry.kind === "repository" || entry.kind === "reader")).toBe(false); + expect(await readFile(path.join(checkout.cwd, "private.txt"), "utf8")).toBe("Private task edits\n"); + }); + + it("requires the admitted actor's write grant for a projectless alias of a protected configured root", async () => { + const f = await fixture(); + await db.update(companyMemberships).set({ membershipRole: "member" }).where(eq(companyMemberships.principalId, f.userId)); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected shared root", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const alias = path.join(root, `${f.issueId}-protected-alias`); + await symlink(f.admission.cwd, alias); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Protected source", cwd: alias }).returning(); + const read = () => assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor }); + await expect(read()).resolves.toBeUndefined(); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/protected|not authorized/); + await expect(f.request({ kind: "url", url: "https://github.com/public/writable" })).rejects.toThrow(/protected|not authorized/); + await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "agent", principalId: f.agentId, + permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/Responsible user.*not authorized/); + const [grant] = await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user", principalId: f.userId, + permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }).returning(); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.service.guardForPublication(f.admission)).resolves.toBeUndefined(); + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, source.id)); + await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + await expect(read()).resolves.toBeUndefined(); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/Responsible user.*not authorized/); + await expect(f.service.guardForPublication(f.admission)).rejects.toThrow(/Responsible user.*not authorized/); + }); + + it("keeps genuine isolated Git copies read-only against their protected source project", async () => { + const f = await fixture(); + const sourceRoot = path.join(root, `${f.issueId}-isolated-source`); + await mkdir(sourceRoot); + const exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", sourceRoot]); + await writeFile(path.join(sourceRoot, "README.md"), "Protected source\n"); + await exec("git", ["-C", sourceRoot, "add", "."]); + await exec("git", ["-C", sourceRoot, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + await exec("git", ["-C", sourceRoot, "worktree", "add", "-b", "isolated-task", f.admission.cwd]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected copy source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Original", cwd: sourceRoot }).returning(); + await db.update(executionWorkspaces).set({ projectId: project.id, projectWorkspaceId: source.id, strategyType: "git_worktree" }).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.service.guardForPublication(f.admission)).resolves.toBeUndefined(); + await db.update(projectWorkspaces).set({ cwd: f.admission.cwd }).where(eq(projectWorkspaces.id, source.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/protected|not authorized/); + }); + + it.each(["move", "delete"] as const)("retains protected shared execution-source write scope after %s", async change => { + const f = await fixture(), source = await standardPeer(f); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected execution owner", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.update(executionWorkspaces).set({ projectId: project.id }).where(eq(executionWorkspaces.id, source.workspace.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/protected|not authorized/); + const grants = await db.insert(principalPermissionGrants).values([ + { companyId: f.companyId, principalType: "agent", principalId: f.agentId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + { companyId: f.companyId, principalType: "user", principalId: f.userId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + ]).returning(); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + if (change === "move") { + const moved = path.join(root, `${source.issueId}-shared-moved`); + await mkdir(moved); + await executionWorkspaceService(db).update(source.workspace.id, { cwd: moved, projectId: null }); + } else await db.delete(executionWorkspaces).where(eq(executionWorkspaces.id, source.workspace.id)); + for (const grant of grants) await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/protected|not authorized|workspace access/); + }); + + it("does not require historical readers to retain write grants for another authorized writer", async () => { + const f = await fixture(), peer = await standardPeer(f); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Shared source writers", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Shared", cwd: f.admission.cwd }); + const prior = await db.insert(principalPermissionGrants).values([ + { companyId: f.companyId, principalType: "agent", principalId: f.agentId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + { companyId: f.companyId, principalType: "user", principalId: f.userId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: f.agentId } }, + ]).returning(); + await f.service.prepareForAdmission(f.admission); + await db.insert(principalPermissionGrants).values([ + { companyId: f.companyId, principalType: "agent", principalId: peer.agentId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: peer.agentId } }, + { companyId: f.companyId, principalType: "user", principalId: peer.userId, permissionKey: "tasks:assign_scope", scope: { projectId: project.id, assigneeAgentId: peer.agentId } }, + ]); + for (const grant of prior) await db.delete(principalPermissionGrants).where(eq(principalPermissionGrants.id, grant.id)); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + await expect(f.service.guardForPublication(peer.admission)).resolves.toBeUndefined(); + }); + + it("checks configured private aliases against both admitted agent and responsible user before capture and publication", async () => { + const f = await fixture(); + const alias = path.join(root, `${f.issueId}-configured-alias`); + await symlink(f.admission.cwd, alias); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private configured files", visibility: "private" }).returning(); + const [configured] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Configured alias", cwd: alias }).returning(); + await writeFile(path.join(f.admission.cwd, "private-configured.txt"), "Private configured project bytes\n"); + await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).rejects.toThrow(/source project/); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project/); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId))).toEqual([]); + const [grant] = await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, + subjectType: "user", subjectId: f.userId }).returning(); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.service.guardForPublication(f.admission)).resolves.toBeUndefined(); + const moved = path.join(root, `${f.issueId}-configured-moved`); + await mkdir(moved); + await db.update(projectWorkspaces).set({ cwd: moved }).where(eq(projectWorkspaces.id, configured.id)); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.id, grant.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project/); + await expect(f.service.guardForPublication(f.admission)).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, configured.id)); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).resolves.toBeUndefined(); + await db.delete(projects).where(eq(projects.id, project.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).rejects.toThrow(/source project/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("retains execution-source project privacy after its workspace project pointer is cleared", async () => { + const f = await fixture(); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Execution source owner", visibility: "private" }).returning(); + await db.update(executionWorkspaces).set({ projectId: project.id }).where(eq(executionWorkspaces.id, f.workspace.id)); + await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }); + const [grant] = await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }).returning(); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await db.update(executionWorkspaces).set({ projectId: null }).where(eq(executionWorkspaces.id, f.workspace.id)); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.id, grant.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values({ companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await db.delete(projects).where(eq(projects.id, project.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project/); + await expect(f.service.guardForPublication(f.admission)).rejects.toThrow(/source project/); + }); + + it.each(["configured", "execution"] as const)("ignores an unrelated %s workspace symlink loop", async kind => { + const f = await fixture(); + const loop = path.join(root, `${f.issueId}-unrelated-loop`); + await symlink(loop, loop); + if (kind === "configured") { + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Unavailable unrelated project", visibility: "private" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Loop", cwd: loop }); + } else { + await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Unrelated loop", cwd: loop, mode: "isolated_workspace", strategyType: "task_directory" }); + } + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).resolves.toBeUndefined(); + }); + + it("rejects an unusable selected root without releasing a retained private source", async () => { + const f = await fixture(); + await privateTask(f); + await f.service.prepareForAdmission(f.admission); + const peer = await standardPeer(f); + const loop = path.join(root, `${f.issueId}-selected-loop`); + await symlink(loop, loop); + await db.update(executionWorkspaces).set({ cwd: loop }).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(f.service.prepareForAdmission({ ...f.admission, cwd: loop })).rejects.toThrow(/ELOOP/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: loop, + sourceWorkspaceId: f.workspace.id, actor: f.actor })).rejects.toThrow(/ELOOP/); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: peer.admission.cwd, + sourceWorkspaceId: peer.workspace.id, actor: peer.actor })).rejects.toThrow(/workspace access|authorization boundary/); + }); + + it("rechecks retained own-workspace privacy without adding an empty-admission membership gate", async () => { + const f = await fixture(); + await db.delete(companyMemberships).where(eq(companyMemberships.companyId, f.companyId)); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(executionWorkspaceRepositoryService(db).prepareForAdmission(f.admission)).resolves.toEqual([]); + const [privateSource] = await db.insert(issues).values({ companyId: f.companyId, title: "Other private writer", visibility: "private", + executionWorkspaceId: f.workspace.id }).returning(); + await db.update(issues).set({ executionWorkspaceId: null }).where(eq(issues.id, privateSource.id)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/workspace access/); + }); + + it("does not publish managed bytes to a retained low-trust reader even with private source grants", async () => { + const f = await fixture(), peer = await standardPeer(f); + await db.update(issues).set({ visibility: "private", privacyRootIssueId: peer.issueId, responsibleUserId: peer.userId, + parentId: f.issueId }).where(eq(issues.id, peer.issueId)); + await db.insert(issueAccessGrants).values([ + { issueId: peer.issueId, subjectType: "agent", subjectId: f.agentId, source: "explicit", grantedByUserId: peer.userId }, + { issueId: peer.issueId, subjectType: "user", subjectId: f.userId, source: "explicit", grantedByUserId: peer.userId }, + ]); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.request({ companyId: f.companyId, issueId: peer.issueId, actor: peer.actor, + request: { repository: { kind: "catalog", id: "private-repo" }, requestKey: randomUUID() } })).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(await f.service.list(f.companyId, peer.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("retains private direct files from an admitted workspace with no repository inventory", async () => { + const f = await fixture(), peer = await standardPeer(f); + await privateTask(f); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await writeFile(path.join(f.admission.cwd, "private-direct.txt"), "Private direct task output"); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-direct-source"), metadata: {} }).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary/); + await grantPeer(f, peer); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + expect(await readFile(path.join(f.admission.cwd, "private-direct.txt"), "utf8")).toBe("Private direct task output"); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("blocks a future private source after an empty standard reader was admitted and moved", async () => { + const f = await fixture(), peer = await standardPeer(f); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-standard-reader"), metadata: {}, status: "archived" }).where(eq(executionWorkspaces.id, peer.workspace.id)); + await privateTask(f); + await expect(executionWorkspaceRepositoryService(db).request({ companyId: f.companyId, issueId: f.issueId, actor: f.actor, + request: { repository: { kind: "catalog", id: "private-repo" }, requestKey: randomUUID() } })).rejects.toThrow(/workspace access|authorization boundary/); + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("rechecks configured-source publication against retained readers after grant revocation", async () => { + const f = await fixture(), peer = await standardPeer(f); + await privateTask(f); + const grants = await grantPeer(f, peer); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + await expect(f.service.guardForPublication(f.admission)).resolves.toBeUndefined(); + await db.update(issueAccessGrants).set({ revokedAt: new Date() }).where(eq(issueAccessGrants.id, grants[0].id)); + await expect(f.service.guardForPublication(f.admission)).rejects.toThrow(/workspace access|authorization boundary/); + await expect(f.service.guardForPublication({ ...f.admission, workspaceId: peer.workspace.id })).rejects.toThrow(/publication binding/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.catalog).not.toHaveBeenCalled(); + }); + + it("fences pending private clones and rechecks granted readers before actual publication", async () => { + const f = await fixture(), peer = await standardPeer(f); + await privateTask(f); + const intent = await f.request({ kind: "catalog", id: "private-repo" }); + let release!: () => void, started!: () => void; + const gate = new Promise(resolve => { release = resolve; }); + const staged = new Promise(resolve => { started = resolve; }); + await localPrivateClone(f, async () => { started(); await gate; }); + const preparation = f.service.prepareForAdmission(f.admission); + const outcome = preparation.then(value => ({ value, error: null }), error => ({ value: null, error })); + try { + await Promise.race([staged, outcome.then(() => { throw new Error("Clone ended before publication barrier"); })]); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary/); + await grantPeer(f, peer); + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + await db.update(issueAccessGrants).set({ revokedAt: new Date() }).where(eq(issueAccessGrants.issueId, f.issueId)); + } finally { release(); } + expect((await outcome).error).toBeTruthy(); + expect(await fs.lstat(path.join(f.admission.cwd, intent.repository.relativePath)).catch(() => null)).toBeNull(); + }); + + it.each(["agent", "issue", "project", "workspace_source_project", "run"] as const)( + "rejects catalog acquisition from raw %s low-trust policy", async source => { + const f = await fixture(); + if (source === "agent") await db.update(agents).set({ permissions: f.policy }).where(eq(agents.id, f.agentId)); + if (source === "issue") await db.update(issues).set({ executionPolicy: f.policy }).where(eq(issues.id, f.issueId)); + if (source === "run") await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + if (source === "project" || source === "workspace_source_project") { + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Restricted", executionWorkspacePolicy: f.policy }).returning(); + if (source === "project") await db.update(issues).set({ projectId: project.id }).where(eq(issues.id, f.issueId)); + else { + await db.update(projects).set({ executionWorkspacePolicy: { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [project.id] } } } }).where(eq(projects.id, project.id)); + await db.update(executionWorkspaces).set({ projectId: project.id, strategyType: "git_worktree" }).where(eq(executionWorkspaces.id, f.workspace.id)); + } + } + await expect(f.request({ kind: "catalog", id: "private-repo" })).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.companyId, f.companyId))).toHaveLength(0); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + }, + ); + + it.each(["agent", "issue", "project", "workspace_source_project", "run"] as const)( + "rejects public acquisition from raw %s policy even with full-auto provider permissions", async source => { + const f = await fixture(); + await db.update(agents).set({ adapterConfig: { provider: "codex", codexPermissionMode: "never" }, + ...(source === "agent" ? { permissions: f.policy } : {}) }).where(eq(agents.id, f.agentId)); + if (source === "issue") await db.update(issues).set({ executionPolicy: f.policy }).where(eq(issues.id, f.issueId)); + if (source === "run") { + await db.update(issues).set({ executionPolicy: {} }).where(eq(issues.id, f.issueId)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + } + if (source === "project" || source === "workspace_source_project") { + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Restricted", executionWorkspacePolicy: f.policy }).returning(); + if (source === "project") await db.update(issues).set({ projectId: project.id }).where(eq(issues.id, f.issueId)); + else { + const admittedPolicy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [project.id] } } }; + await db.update(projects).set({ executionWorkspacePolicy: admittedPolicy }).where(eq(projects.id, project.id)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: admittedPolicy } }).where(eq(heartbeatRuns.id, f.runId)); + await db.update(executionWorkspaces).set({ projectId: project.id, strategyType: "git_worktree" }).where(eq(executionWorkspaces.id, f.workspace.id)); + } + } + for (const url of ["https://github.com/public/example", "https://github.com/sponsor/private"]) { + await expect(f.request({ kind: "url", url })).rejects.toThrow(/trusted task/); + } + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + }); + + it.each(["pending", "preparing"] as const)("blocks %s public acquisition after trust is downgraded", async state => { + const f = await fixture(); + const receipt = await f.request({ kind: "url", url: "https://github.com/public/example" }); + await db.update(executionWorkspaceRepositories).set({ state }).where(eq(executionWorkspaceRepositories.id, receipt.operationId)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/trusted task/); + expect((await f.service.list(f.companyId, f.workspace.id))[0].state).toBe(state); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + }); + + it("rejects an unsponsored request even when the caller supplies a sponsor actor", async () => { + const f = await fixture(); + await db.update(heartbeatRuns).set({ responsibleUserId: null }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.request({ kind: "url", url: "https://github.com/public/example" })).rejects.toThrow(/responsible user/); + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + await expect(f.service.prepareForAdmission({ ...f.admission, responsibleUserId: null })).resolves.toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it.each(["pending", "ready"] as const)("blocks a %s managed receipt after the run becomes low trust", async state => { + const f = await fixture(); + const receipt = await f.request({ kind: "catalog", id: "private-repo" }); + await db.update(executionWorkspaceRepositories).set({ state, pinnedCommit: state === "ready" ? "a".repeat(40) : null }) + .where(eq(executionWorkspaceRepositories.id, receipt.operationId)); + const checkout = path.join(f.admission.cwd, receipt.repository.relativePath); + if (state === "ready") { await mkdir(checkout, { recursive: true }); await writeFile(path.join(checkout, "private.txt"), "retained private bytes"); } + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.request({ kind: "url", url: receipt.repository.repoUrl })).rejects.toThrow(/anonymous public URLs only|trusted task/); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + if (state === "ready") expect(await readFile(path.join(checkout, "private.txt"), "utf8")).toBe("retained private bytes"); + }); + + it.each([ + { status: "active", closedAt: new Date() }, + { status: "idle", closedAt: new Date() }, + { status: "archived", closedAt: null }, + { status: "in_review", closedAt: null }, + ])("rejects repository requests for unavailable $status workspaces (closed=$closedAt)", async lifecycle => { + const f = await fixture(); + await db.update(executionWorkspaces).set(lifecycle).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(f.request({ kind: "url", url: "https://github.com/public/example" })).rejects.toThrow("Execution workspace is unavailable"); + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it.each(["sponsored", "local_board", "routine"] as const)("prepares public Git from an idle workspace for a trusted %s task and reuses verified bytes after trust downgrade", async principal => { + const f = await fixture(); + await db.update(executionWorkspaces).set({ status: "idle" }).where(eq(executionWorkspaces.id, f.workspace.id)); + if (principal === "local_board") await db.update(heartbeatRuns).set({ responsibleUserId: "local-board" }).where(eq(heartbeatRuns.id, f.runId)); + const receipt = await f.request({ kind: "url", url: "https://github.com/public/example" }); + const responsibleUserId = principal === "local_board" ? "local-board" : principal === "routine" ? null : f.userId; + await db.update(heartbeatRuns).set({ responsibleUserId }).where(eq(heartbeatRuns.id, f.runId)); + const admission = { ...f.admission, responsibleUserId }; + const source = path.join(root, `${f.issueId}-public-source`); + await mkdir(source); + const exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", source]); + await writeFile(path.join(source, "public.txt"), "Public fixture bytes\n"); + await exec("git", ["-C", source, "add", "."]); + await exec("git", ["-C", source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "public source"]); + const actual = await vi.importActual("../services/managed-repository-checkout.js"); + acquisition.materialize.mockImplementationOnce(async (cwd, input) => { + const auth = await input.resolveGitAuth(input.repoUrl); + expect(auth.env).toMatchObject({ GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" }); + expect(auth.configArgs).toEqual(buildGitAuthInvocation({ token: "", source: "managed_connection", secretName: null }).configArgs); + const result = await actual.materializeManagedProjectWorkspace(cwd, { ...input, localSource: source }); + await exec("git", ["-C", cwd, "remote", "set-url", "origin", input.repoUrl]); + return result; + }); + const [prepared] = await f.service.prepareForAdmission(admission); + expect(prepared.id).toBe(receipt.operationId); + expect(await readFile(path.join(prepared.cwd, "public.txt"), "utf8")).toBe("Public fixture bytes\n"); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + await writeFile(path.join(prepared.cwd, "committed.txt"), "Later task commit\n"); + await exec("git", ["-C", prepared.cwd, "add", "committed.txt"]); + await exec("git", ["-C", prepared.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "task changes"]); + const editedHead = (await exec("git", ["-C", prepared.cwd, "rev-parse", "HEAD"])).stdout.trim(); + expect(editedHead).not.toBe(prepared.pinnedCommit); + await writeFile(path.join(prepared.cwd, "dirty.txt"), "Uncommitted task bytes\n"); + acquisition.materialize.mockClear(); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.prepareForAdmission(admission)).resolves.toEqual([prepared]); + await expect(f.service.reserveForRecovery(admission)).resolves.toBeUndefined(); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(await readFile(path.join(prepared.cwd, "public.txt"), "utf8")).toBe("Public fixture bytes\n"); + expect((await exec("git", ["-C", prepared.cwd, "rev-parse", "HEAD"])).stdout.trim()).toBe(editedHead); + expect(await readFile(path.join(prepared.cwd, "dirty.txt"), "utf8")).toBe("Uncommitted task bytes\n"); + await rm(prepared.cwd, { recursive: true }); + await expect(f.service.prepareForAdmission(admission)).rejects.toThrow(/trusted task/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("reauthorizes a public clone before publication after trust changes during Git", async () => { + const f = await fixture(); + const receipt = await f.request({ kind: "url", url: "https://github.com/public/example" }); + await localPrivateClone(f, async () => { + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + }); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/trusted task/); + await expect(fs.lstat(path.join(f.admission.cwd, receipt.repository.relativePath))).rejects.toMatchObject({ code: "ENOENT" }); + expect((await f.service.list(f.companyId, f.workspace.id))[0]).toMatchObject({ pinnedCommit: null, state: "needs_repair" }); + expect(acquisition.materialize).toHaveBeenCalledOnce(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + }); + + it.each(["same_repository", "different_repository", "empty_inventory", "unpinned_publication", "pending_intent"] as const)( + "rejects managed peer bytes in the same physical root with %s", async scenario => { + const f = await fixture(); + const managed = await f.request({ kind: "catalog", id: "private-repo" }); + const checkout = path.join(f.admission.cwd, managed.repository.relativePath); + if (scenario !== "pending_intent") { + await mkdir(path.join(checkout, ".git"), { recursive: true }); + await writeFile(path.join(checkout, "private.txt"), "Private peer bytes"); + } else { + await db.delete(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + } + if (scenario !== "unpinned_publication" && scenario !== "pending_intent") await db.update(executionWorkspaceRepositories) + .set({ state: "ready", pinnedCommit: "b".repeat(40) }).where(eq(executionWorkspaceRepositories.id, managed.operationId)); + const [peer] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Same physical folder", + cwd: f.admission.cwd, mode: "isolated_workspace", strategyType: "task_directory" }).returning(); + const issueId = randomUUID(), runId = randomUUID(); + await db.insert(issues).values({ id: issueId, companyId: f.companyId, title: "Low-trust peer", assigneeAgentId: f.agentId, + executionWorkspaceId: peer.id }); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, rootIssueId: issueId } } }; + await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, status: "running", + responsibleUserId: f.userId, contextSnapshot: { issueId, executionPolicy: policy } }); + await db.update(issues).set({ executionRunId: runId }).where(eq(issues.id, issueId)); + if (scenario !== "empty_inventory") await expect(f.service.request({ companyId: f.companyId, issueId, actor: { ...f.actor, runId }, + request: { requestKey: randomUUID(), repository: { kind: "url", url: scenario === "same_repository" ? managed.repository.repoUrl : "https://github.com/public/other" } } })) + .rejects.toThrow(/anonymous public URLs only|trusted task/); + await expect(f.service.prepareForAdmission({ ...f.admission, issueId, runId, workspaceId: peer.id })).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + if (scenario !== "pending_intent") expect(await readFile(path.join(checkout, "private.txt"), "utf8")).toBe("Private peer bytes"); + }, + ); + + it.each([ + { marker: "valid", reserved: false }, { marker: "malformed", reserved: false }, { marker: "symlink", reserved: false }, + { marker: "valid", reserved: true }, { marker: "malformed", reserved: true }, { marker: "symlink", reserved: true }, + ])("rejects readable private publication behind an unreadable alias ($marker marker, reserved=$reserved)", async ({ marker, reserved }) => { + const f = await fixture(); + const receipt = await f.request({ kind: "catalog", id: "private-repo" }); + await db.delete(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + const checkout = path.join(f.admission.cwd, receipt.repository.relativePath); + await mkdir(checkout, { recursive: true }); + const exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", checkout]); + await writeFile(path.join(checkout, "private.txt"), "Private published bytes must never enter low-trust capture\n"); + await exec("git", ["-C", checkout, "add", "."]); + await exec("git", ["-C", checkout, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "private source"]); + const pin = (await exec("git", ["-C", checkout, "rev-parse", "HEAD"])).stdout.trim(); + const owner = path.join(checkout, ".git", "paperclip-workspace-owner.json"); + const ownerJson = JSON.stringify({ version: 1, repositoryId: receipt.operationId, pinnedCommit: pin }); + if (marker === "symlink") { + const target = path.join(root, `${f.issueId}-untrusted-owner`); + await writeFile(target, ownerJson); + await symlink(target, owner); + } else await writeFile(owner, marker === "valid" ? ownerJson : "{unverifiable owner"); + await db.update(executionWorkspaceRepositories).set({ state: "ready", pinnedCommit: pin }).where(eq(executionWorkspaceRepositories.id, receipt.operationId)); + const aliasParent = path.join(root, `${f.issueId}-unreadable`); + await mkdir(aliasParent); + const alias = path.join(aliasParent, "workspace"); + await symlink(f.admission.cwd, alias); + await db.update(executionWorkspaces).set({ cwd: alias }).where(eq(executionWorkspaces.id, f.workspace.id)); + const peer = await lowTrustPeer(f); + const canonicalCwd = await realpath(f.admission.cwd); + if (reserved) await db.insert(executionWorkspaceRepositoryRoots).values({ companyId: f.companyId, canonicalCwd, + rootKey: createHash("sha256").update(canonicalCwd).digest("hex"), mode: "anonymous_only" }); + const actual = fs.realpath.bind(fs); + vi.spyOn(fs, "realpath").mockImplementation(async pathname => { + if (String(pathname).startsWith(aliasParent)) throw Object.assign(new Error("Unreadable legacy alias"), { code: "EACCES" }); + return actual(pathname); + }); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + await expect(f.service.request({ companyId: f.companyId, issueId: peer.admission.issueId, actor: peer.actor, + request: { repository: { kind: "url", url: "https://github.com/public/unrelated" }, requestKey: randomUUID() } })) + .rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(await f.service.list(f.companyId, peer.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + expect(await readFile(path.join(checkout, "private.txt"), "utf8")).toContain("Private published bytes"); + }); + + it.each(["standard", "retained_low_trust", "current_low_trust"] as const)("recovers an exact legacy run-ID binding with %s trust", async trust => { + const f = await fixture(); + const execution = buildNativeExecutionInput({ + companyId: f.companyId, runId: f.runId, agentId: f.agentId, + issue: { id: f.issueId, identifier: "LEG-1", title: "Legacy recovery", description: null, workMode: "standard" }, + taskPrompt: "Continue", normalizedSessionId: randomUUID(), provider: "codex", + workspace: { id: f.runId, cwd: f.admission.cwd, repoUrl: null, repoRef: null, branchName: null }, + completionContract: { id: randomUUID(), sha256: `sha256:${"a".repeat(64)}`, schemaVersion: "paperclip.run-result.v1", contract: { revision: "1", objective: "Recover", criteria: [{ id: "objective", requirement: "Keep the admitted folder" }] } }, + runtimeContext: nativeRuntimeContextFixture(), + }); + await db.update(heartbeatRuns).set({ runtimeMode: "native", runnerProfileJson: { nativeExecutionInput: execution }, + contextSnapshot: { issueId: f.issueId, ...(trust === "retained_low_trust" ? { executionPolicy: f.policy } : {}) } }).where(eq(heartbeatRuns.id, f.runId)); + if (trust === "current_low_trust") await db.update(agents).set({ permissions: f.policy }).where(eq(agents.id, f.agentId)); + await expect(f.service.reserveForRecovery({ ...f.admission, workspaceId: f.runId })).resolves.toBeUndefined(); + const reservations = await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + expect(reservations).toHaveLength(trust === "standard" ? 0 : 1); + if (trust !== "standard") expect(reservations[0].mode).toBe("anonymous_only"); + await expect(f.service.reserveForRecovery({ ...f.admission, workspaceId: randomUUID() })).rejects.toThrow(/recovery identity/); + execution.binding.agentId = randomUUID(); + await db.update(heartbeatRuns).set({ runnerProfileJson: { nativeExecutionInput: execution } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.reserveForRecovery({ ...f.admission, workspaceId: f.runId })).rejects.toThrow(/recovery identity/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it.each(["ENOTDIR", "EACCES"] as const)("ignores unrelated unavailable legacy managed paths (%s) while preserving anonymous roots", async code => { + const f = await fixture(); + await f.request({ kind: "catalog", id: "private-repo" }); + await db.delete(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + const peerRoot = path.join(root, `${f.issueId}-anonymous`); + await mkdir(peerRoot); + const peer = await lowTrustPeer(f, peerRoot); + const unavailableParent = path.join(root, `${f.issueId}-unavailable`); + await writeFile(unavailableParent, "Archived directory replaced by a file"); + const unavailableCwd = path.join(unavailableParent, "checkout"); + await db.update(executionWorkspaces).set({ cwd: unavailableCwd, status: "archived" }).where(eq(executionWorkspaces.id, f.workspace.id)); + if (code === "EACCES") { + const actual = fs.realpath.bind(fs); + vi.spyOn(fs, "realpath").mockImplementation(async pathname => { + if (String(pathname).startsWith(unavailableParent)) throw Object.assign(new Error("fixture access denied"), { code: "EACCES" }); + return actual(pathname); + }); + } + await expect(f.service.prepareForAdmission(peer.admission)).resolves.toEqual([]); + vi.restoreAllMocks(); + const actual = fs.realpath.bind(fs); + vi.spyOn(fs, "realpath").mockImplementation(async pathname => { + if (String(pathname) === unavailableCwd) throw Object.assign(new Error("Historical path remains unavailable"), { code: "EACCES" }); + return actual(pathname); + }); + await expect(executionWorkspaceRepositoryService(db).prepareForAdmission(peer.admission)).resolves.toEqual([]); + }); + + it("still rejects a legacy pending managed receipt through a canonical path alias", async () => { + const f = await fixture(); + await f.request({ kind: "catalog", id: "private-repo" }); + await db.delete(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + const alias = path.join(root, `${f.issueId}-legacy-alias`); + await symlink(f.admission.cwd, alias); + await db.update(executionWorkspaces).set({ cwd: alias }).where(eq(executionWorkspaces.id, f.workspace.id)); + const peer = await lowTrustPeer(f); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + }); + + it("reserves a legacy low-trust recovery folder after its task binding moves", async () => { + const f = await fixture(); + const peer = await lowTrustPeer(f); + await db.update(issues).set({ parentId: peer.admission.issueId }).where(eq(issues.id, f.issueId)); + await db.update(issues).set({ executionWorkspaceId: null }).where(eq(issues.id, peer.admission.issueId)); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-recovery"), metadata: {}, status: "archived" }) + .where(eq(executionWorkspaces.id, peer.workspace.id)); + await f.service.reserveForRecovery(peer.admission); + await expect(executionWorkspaceRepositoryService(db).request({ companyId: f.companyId, issueId: f.issueId, actor: f.actor, + request: { repository: { kind: "catalog", id: "private-repo" }, requestKey: randomUUID() } })) + .rejects.toThrow(/anonymous public URLs only|trusted task/); + const [reservation] = await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + expect(reservation).toMatchObject({ canonicalCwd: await realpath(f.admission.cwd), mode: "anonymous_only" }); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + }); + + it("reserves a legacy managed recovery folder before a low-trust peer resumes", async () => { + const f = await fixture(); + await f.request({ kind: "catalog", id: "private-repo" }); + await db.delete(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + const peer = await lowTrustPeer(f); + await f.service.reserveForRecovery(f.admission); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-managed-recovery") }).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(executionWorkspaceRepositoryService(db).reserveForRecovery(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("serializes a managed request against anonymous admission through physical aliases", async () => { + const f = await fixture(); + const alias = path.join(root, `${f.issueId}-alias`); + await symlink(f.admission.cwd, alias); + const peer = await lowTrustPeer(f, alias); + await db.update(issues).set({ parentId: peer.admission.issueId }).where(eq(issues.id, f.issueId)); + const outcomes = await Promise.allSettled([ + f.service.prepareForAdmission(peer.admission), + f.request({ kind: "catalog", id: "private-repo" }), + ]); + expect(outcomes.filter(result => result.status === "fulfilled")).toHaveLength(1); + expect(outcomes.filter(result => result.status === "rejected")).toHaveLength(1); + const [reservation] = await db.select().from(executionWorkspaceRepositoryRoots).where(eq(executionWorkspaceRepositoryRoots.companyId, f.companyId)); + expect(reservation.mode).toBe(outcomes[0].status === "fulfilled" ? "anonymous_only" : "managed"); + const restarted = executionWorkspaceRepositoryService(db); + if (reservation.mode === "anonymous_only") { + await expect(f.request({ kind: "catalog", id: "private-repo" })).rejects.toThrow(/anonymous public URLs only|trusted task/); + await expect(restarted.prepareForAdmission(peer.admission)).resolves.toEqual([]); + } else await expect(restarted.prepareForAdmission(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("keeps anonymous root ownership across workspace moves and blocks future catalog promotion", async () => { + const f = await fixture(); + const pending = await f.request({ kind: "url", url: "https://github.com/public/later-catalog" }); + const peer = await lowTrustPeer(f); + await db.update(issues).set({ parentId: peer.admission.issueId }).where(eq(issues.id, f.issueId)); + await f.service.prepareForAdmission(peer.admission); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-anonymous-workspace"), metadata: {}, status: "archived" }) + .where(eq(executionWorkspaces.id, peer.workspace.id)); + const restarted = executionWorkspaceRepositoryService(db); + await expect(restarted.request({ companyId: f.companyId, issueId: f.issueId, actor: f.actor, + request: { repository: { kind: "catalog", id: "private-repo" }, requestKey: randomUUID() } })).rejects.toThrow(/anonymous public URLs only|trusted task/); + acquisition.catalog.mockResolvedValue({ repositories: [{ id: "later", fullName: "public/later-catalog", private: true, + url: pending.repository.repoUrl, connections: ["sponsor-github"] }] }); + await expect(restarted.prepareForAdmission(f.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + expect(acquisition.managedAuth).not.toHaveBeenCalled(); + expect(acquisition.materialize).not.toHaveBeenCalled(); + expect((await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.id, pending.operationId)))[0].catalogRepositoryId).toBeNull(); + }); + + it("rejects anonymous admission while a managed clone is waiting to publish", async () => { + const f = await fixture(); + const receipt = await f.request({ kind: "catalog", id: "private-repo" }); + const peer = await lowTrustPeer(f); + const source = path.join(root, `${f.issueId}-private-source`); + await mkdir(source); + const exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", source]); + await writeFile(path.join(source, "private.txt"), "Private fixture bytes\n"); + await exec("git", ["-C", source, "add", "."]); + await exec("git", ["-C", source, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "private source"]); + let release!: () => void, started!: () => void; + const gate = new Promise(resolve => { release = resolve; }); + const staged = new Promise(resolve => { started = resolve; }); + const actual = await vi.importActual("../services/managed-repository-checkout.js"); + acquisition.materialize.mockImplementationOnce(async (cwd, input) => { + const result = await actual.materializeManagedProjectWorkspace(cwd, { ...input, localSource: source, + beforePublish: async cloneCwd => { + await input.beforePublish(cloneCwd); + started(); + await gate; + } }); + await exec("git", ["-C", cwd, "remote", "set-url", "origin", input.repoUrl]); + return result; + }); + const preparation = f.service.prepareForAdmission(f.admission); + try { + await Promise.race([staged, preparation.then(() => { throw new Error("Managed preparation finished before publication barrier"); })]); + const [pending] = await db.select().from(executionWorkspaceRepositories).where(eq(executionWorkspaceRepositories.id, receipt.operationId)); + expect(pending).toMatchObject({ state: "preparing", pinnedCommit: null }); + await db.update(executionWorkspaces).set({ cwd: path.join(root, "moved-managed-workspace"), metadata: {} }).where(eq(executionWorkspaces.id, f.workspace.id)); + await expect(executionWorkspaceRepositoryService(db).prepareForAdmission(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + } finally { release(); } + const [prepared] = await preparation; + expect(await readFile(path.join(prepared.cwd, "private.txt"), "utf8")).toBe("Private fixture bytes\n"); + await expect(executionWorkspaceRepositoryService(db).prepareForAdmission(peer.admission)).rejects.toThrow(/anonymous public URLs only|trusted task/); + }); + + it("retains previous run trust without confusing a board request's reassigned agent", async () => { + const f = await fixture(); + const [replacement] = await db.insert(agents).values({ companyId: f.companyId, name: "Replacement", adapterType: "process" }).returning(); + await db.update(issues).set({ assigneeAgentId: replacement.id }).where(eq(issues.id, f.issueId)); + await db.update(heartbeatRuns).set({ status: "succeeded", contextSnapshot: { taskId: f.issueId } }).where(eq(heartbeatRuns.id, f.runId)); + const request = () => f.service.request({ companyId: f.companyId, issueId: f.issueId, + actor: { type: "board", source: "local_implicit", userId: "local-board" }, + request: { requestKey: randomUUID(), repository: { kind: "catalog", id: "private-repo" } } }); + await expect(request()).resolves.toMatchObject({ kind: "requires_next_admission" }); + await db.update(heartbeatRuns).set({ contextSnapshot: { taskId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(request()).rejects.toThrow(/anonymous public URLs only|trusted task/); + }); + + it("preserves standard empty-inventory admission without repository mutation authority", async () => { + const f = await fixture(); + await db.delete(companyMemberships).where(eq(companyMemberships.companyId, f.companyId)); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + expect(acquisition.catalog).not.toHaveBeenCalled(); + expect(acquisition.materialize).not.toHaveBeenCalled(); + await db.insert(executionWorkspaceRepositories).values({ companyId: f.companyId, executionWorkspaceId: f.workspace.id, + requestedByIssueId: f.issueId, repositoryIdentity: "https://github.com/public/example", repoUrl: "https://github.com/public/example", + relativePath: ".paperclip-repositories/unused", requestedRef: "HEAD", requestKey: "requires-authority", requestKeys: ["requires-authority"] }); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("keeps low-trust empty-inventory admission free of acquisition-only filesystem and membership requirements", async () => { + const f = await fixture(); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await db.delete(companyMemberships).where(eq(companyMemberships.companyId, f.companyId)); + await expect(f.service.prepareForAdmission({ ...f.admission, cwd: path.join(root, "not-materialized") })).resolves.toEqual([]); + expect(acquisition.catalog).not.toHaveBeenCalled(); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("rejects admission without the persisted task run before materialization", async () => { + const f = await fixture(); + await f.request({ kind: "catalog", id: "private-repo" }); + await expect(f.service.prepareForAdmission({ ...f.admission, runId: randomUUID() })).rejects.toThrow(/run identity/); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/task-workspace-repositories.test.ts b/server/src/__tests__/task-workspace-repositories.test.ts new file mode 100644 index 00000000000..57e4e87d789 --- /dev/null +++ b/server/src/__tests__/task-workspace-repositories.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { prepareWorkspaceRepositorySchema } from "@paperclipai/shared"; +import { resolveTaskRepository, taskRepositoryRelativePath, assertRepositoryRetryMatches } from "../services/execution-workspace-repositories.js"; + +const repository = { id: "123", fullName: "team/source", url: "https://github.com/team/source", connections: [] }; +describe("task repository acquisition authority", () => { + it("requires a currently authorized catalog ID, including on retry after revocation", () => { + expect(resolveTaskRepository({ kind: "catalog", id: "123" }, [repository])).toMatchObject({ catalogRepositoryId: "123" }); + expect(() => resolveTaskRepository({ kind: "catalog", id: "123" }, [])).toThrow(/no longer available/); + expect(() => resolveTaskRepository({ kind: "catalog", id: "other-company-repo" }, [repository])).toThrow(/no longer available/); + }); + it("normalizes HTTPS URLs and never accepts credentials, alternate hosts, or shell paths", () => { + expect(resolveTaskRepository({ kind: "url", url: "https://github.com/team/source.git" }, [repository])).toMatchObject({ catalogRepositoryId: "123", repositoryIdentity: repository.url }); + expect(resolveTaskRepository({ kind: "url", url: "https://github.com/public/repo" }, [])).toMatchObject({ catalogRepositoryId: null }); + for (const url of ["git@github.com:team/source", "https://token@github.com/team/source", "https://evil.example/team/source", "file:///tmp/source", "https://github.com/team/source?q=secret"]) { + expect(() => resolveTaskRepository({ kind: "url", url }, [])).toThrow(/HTTPS GitHub/); + } + }); + it("converges repository aliases into a stable contained path and rejects conflicting refs", () => { + const source = resolveTaskRepository({ kind: "catalog", id: "123" }, [repository]); + const alias = resolveTaskRepository({ kind: "url", url: "https://github.com/TEAM/SOURCE.git" }, [repository]); + expect(taskRepositoryRelativePath(source.repositoryIdentity)).toBe(taskRepositoryRelativePath(alias.repositoryIdentity)); + expect(taskRepositoryRelativePath(source.repositoryIdentity)).toMatch(/^\.paperclip-repositories\/task-repo-[a-f0-9]{24}$/); + const receipt = { repositoryIdentity: source.repositoryIdentity, requestedRef: "main" }; + expect(() => assertRepositoryRetryMatches(receipt, source.repositoryIdentity, "main")).not.toThrow(); + expect(() => assertRepositoryRetryMatches(receipt, source.repositoryIdentity, "release")).toThrow(/different ref/); + expect(() => assertRepositoryRetryMatches(receipt, "another", "main")).toThrow(/conflicts/); + }); + it("accepts refs as data while rejecting options and traversal", () => { + const parse = (ref: string) => prepareWorkspaceRepositorySchema.parse({ repository: { kind: "catalog", id: "123" }, requestKey: "retry", ref }); + expect(parse("feature/task-files").ref).toBe("feature/task-files"); + for (const ref of ["--upload-pack=sh", "../main", "a..b", "main;echo x", "refs//heads/main"]) expect(() => parse(ref)).toThrow(); + }); +}); diff --git a/server/src/__tests__/task-workspace-source-copy-privacy.test.ts b/server/src/__tests__/task-workspace-source-copy-privacy.test.ts new file mode 100644 index 00000000000..ad2e87b789b --- /dev/null +++ b/server/src/__tests__/task-workspace-source-copy-privacy.test.ts @@ -0,0 +1,732 @@ +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { createHash, randomUUID } from "node:crypto"; +import { mkdtemp, mkdir, writeFile, readFile, realpath, rm, symlink } from "node:fs/promises"; +import fs from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { eq } from "drizzle-orm"; +import { agents, authUsers, companies, companyMemberships, createDb, executionWorkspaceRepositoryRoots, executionWorkspaceRepositoryAccess, executionWorkspaceRepositories, executionWorkspaces, + heartbeatRuns, issueAccessGrants, issues, projects, projectWorkspaces, projectAccessMembers, principalPermissionGrants, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS } from "@paperclipai/db"; +import { assertExecutionWorkspaceRootReadAccess, assertExecutionWorkspaceRootWriteAccess, assertIsolatedWorkspaceSourceReadAccess, retainIsolatedWorkspaceSourceAccess, executionWorkspaceRepositoryService } from "../services/execution-workspace-repositories.js"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { projectService } from "../services/projects.js"; +import { buildNativeExecutionInput } from "../services/native-runtime/native-execution-input.js"; +import { nativeRuntimeContextFixture } from "../services/native-runtime/runtime-context.test-fixture.js"; +import { buildGitAuthInvocation } from "../services/git-credentials.js"; +import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; + +import { createRepositoryTrustFixture } from "./helpers/repository-trust-fixture.js"; + +const acquisition = vi.hoisted(() => ({ managedAuth: vi.fn(), materialize: vi.fn(), catalog: vi.fn() })); +vi.mock("../services/tool-access.js", () => ({ toolAccessService: () => ({ listProjectRepositories: acquisition.catalog }) })); +vi.mock("../services/git-credentials.js", async (original) => ({ + ...await original(), + createGitRemoteAuthProvider: () => acquisition.managedAuth, +})); +vi.mock("../services/managed-repository-checkout.js", async (original) => ({ + ...await original(), + materializeManagedProjectWorkspace: acquisition.materialize, +})); + +const support = await getEmbeddedPostgresTestSupport(); +(support.supported ? describe : describe.skip)("task workspace source copy privacy", () => { + const context = createRepositoryTrustFixture(acquisition); + let db: ReturnType; + let root: string; + beforeAll(async () => { ({ db, root } = await context.setup()); }, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS); + afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); }); + afterAll(context.cleanup); + const { fixture, lowTrustPeer, standardPeer, privateTask, grantPeer, localPrivateClone } = context; + + it.each(["current", "retained", "standard_run", "absent_run_policy", "narrower_boundary"] as const)("rejects incompatible physical project trust (%s) without changing file read authority", async scenario => { + const f = await fixture(); + const projectId = randomUUID(); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [projectId] } } }; + const [owner] = await db.insert(projects).values({ id: projectId, companyId: f.companyId, name: "Low-trust physical owner", executionWorkspacePolicy: policy }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: owner.id, name: "Physical source", cwd: f.admission.cwd, isPrimary: true }).returning(); + if (scenario === "retained") await projectService(db).updateWorkspace(owner.id, source.id, { cwd: path.join(root, randomUUID()) }); + if (scenario === "standard_run" || scenario === "absent_run_policy") { + await db.update(issues).set({ executionPolicy: policy }).where(eq(issues.id, f.issueId)); + if (scenario === "standard_run") await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: { trustPreset: "standard" } } }).where(eq(heartbeatRuns.id, f.runId)); + } + if (scenario === "narrower_boundary") { + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, trustBoundary: { + mode: "low_trust_review", companyId: f.companyId, issueIds: [f.issueId], projectIds: [owner.id], + } } }; + await db.update(issues).set({ executionPolicy: policy }).where(eq(issues.id, f.issueId)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }).where(eq(heartbeatRuns.id, f.runId)); + } + await expect(assertExecutionWorkspaceRootReadAccess(db, { ...f.admission, actor: f.actor, sourceWorkspaceId: f.workspace.id })).resolves.toBeUndefined(); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { ...f.admission, actor: f.actor, sourceWorkspaceId: f.workspace.id })).rejects.toThrow("Physical workspace source trust is incompatible"); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow("Physical workspace source trust is incompatible"); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("keeps a declared low-trust source compatible with its retained effective policy", async () => { + const f = await fixture(); + const projectId = randomUUID(); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [projectId] } } }; + const [owner] = await db.insert(projects).values({ id: projectId, companyId: f.companyId, name: "Declared low-trust source", executionWorkspacePolicy: policy }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: owner.id, name: "Source", cwd: f.admission.cwd, isPrimary: true }); + await db.update(executionWorkspaces).set({ projectId: owner.id }).where(eq(executionWorkspaces.id, f.workspace.id)); + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it.each(["writer", "retained_reader"] as const)("rejects incompatible %s trust before copy provisioning without minting copy proof", async principal => { + const f = await fixture(), exec = promisify(execFileCallback); + if (principal === "retained_reader") await f.service.prepareForAdmission(f.admission); + const sourceCwd = path.join(root, `${f.issueId}-low-trust-source`); + await mkdir(sourceCwd); + await exec("git", ["init", "--initial-branch=main", sourceCwd]); + await writeFile(path.join(sourceCwd, "source.txt"), "Low-trust source\n"); + await exec("git", ["-C", sourceCwd, "add", "."]); + await exec("git", ["-C", sourceCwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + await rm(f.admission.cwd, { recursive: true }); + await exec("git", ["-C", sourceCwd, "worktree", "add", "-b", "target", f.admission.cwd]); + const projectId = randomUUID(); + const policy = { ...f.policy, authorizationPolicy: { ...f.policy.authorizationPolicy, + trustBoundary: { ...f.policy.authorizationPolicy.trustBoundary, projectIds: [projectId] } } }; + await db.insert(projects).values({ id: projectId, companyId: f.companyId, name: "Low-trust copied source", executionWorkspacePolicy: policy }); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId, name: "Source", cwd: sourceCwd }); + let actor = f.actor; + if (principal === "retained_reader") { + const runId = randomUUID(); + await db.update(issues).set({ executionPolicy: policy }).where(eq(issues.id, f.issueId)); + await db.insert(heartbeatRuns).values({ id: runId, companyId: f.companyId, agentId: f.agentId, responsibleUserId: f.userId, + status: "running", contextSnapshot: { issueId: f.issueId, executionPolicy: policy } }); + actor = { ...f.actor, runId }; + } + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd, actor }); + await expect(retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd, targetCwd: f.admission.cwd, + actor, issueId: f.issueId, captured, phase: "verified" })).rejects.toThrow("Physical workspace source trust is incompatible"); + const entries = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(entries.some(entry => entry.kind === "isolated_project_source" && entry.scopeId === projectId)).toBe(true); + expect(entries.some(entry => entry.kind === "isolated_copy")).toBe(false); + expect(await readFile(path.join(f.admission.cwd, "source.txt"), "utf8")).toBe("Low-trust source\n"); + }); + + it.each([ + { owner: "configured", direction: "descendant" }, { owner: "configured", direction: "ancestor" }, + { owner: "execution", direction: "descendant" }, { owner: "execution", direction: "ancestor" }, + ])("authorizes newly overlapping private $owner $direction owners before selection, admission and publication", async ({ owner, direction }) => { + const f = await fixture(); + const child = path.join(f.admission.cwd, "private"); + await mkdir(child); + const privateCwd = direction === "descendant" ? child : f.admission.cwd; + if (direction === "ancestor") { + f.admission.cwd = child; + await db.update(executionWorkspaces).set({ cwd: child }).where(eq(executionWorkspaces.id, f.workspace.id)); + } + await f.service.prepareForAdmission(f.admission); + let grant: () => Promise; + let detach: () => Promise; + if (owner === "configured") { + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Nested private source", visibility: "private" }).returning(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private files", cwd: privateCwd }).returning(); + grant = () => db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + detach = async () => { + await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, source.id)); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + }; + } else { + const peer = await standardPeer(f); + await db.update(executionWorkspaces).set({ cwd: privateCwd }).where(eq(executionWorkspaces.id, peer.workspace.id)); + await db.update(issues).set({ visibility: "private", privacyRootIssueId: peer.issueId, responsibleUserId: peer.userId }).where(eq(issues.id, peer.issueId)); + grant = () => db.insert(issueAccessGrants).values([ + { issueId: peer.issueId, subjectType: "agent", subjectId: f.agentId, source: "explicit", grantedByUserId: peer.userId }, + { issueId: peer.issueId, subjectType: "user", subjectId: f.userId, source: "explicit", grantedByUserId: peer.userId }, + ]); + detach = async () => { + const moved = path.join(root, `${peer.issueId}-moved`); await mkdir(moved); + await executionWorkspaceService(db).update(peer.workspace.id, { cwd: moved }); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, peer.issueId)); + }; + } + await writeFile(path.join(privateCwd, "private.txt"), "Nested private files\n"); + const read = () => assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, actor: f.actor, sourceWorkspaceId: f.workspace.id }); + const select = () => executionWorkspaceService(db).selectTaskWorkspace({ companyId: f.companyId, issueId: f.issueId, actor: f.actor, + selection: { kind: "existing", workspaceId: f.workspace.id }, expectedBindingRevision: 0, requestKey: randomUUID() }); + await expect(read()).rejects.toThrow(/source project|workspace access|authorization boundary/); + await expect(select()).rejects.toThrow(/source project|workspace access|authorization boundary/); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project|workspace access|authorization boundary/); + await expect(f.service.guardForPublication(f.admission)).rejects.toThrow(/source project|workspace access|authorization boundary/); + await grant(); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(read()).resolves.toBeUndefined(); + await detach(); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/source project|workspace access|authorization boundary/); + await expect(read()).rejects.toThrow(/source project|workspace access|authorization boundary/); + }); + + it("does not confuse sibling component prefixes with overlapping roots", async () => { + const f = await fixture(); + const sibling = `${f.admission.cwd}er`; await mkdir(sibling); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Unrelated private source", visibility: "private" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Sibling", cwd: sibling }); + await db.insert(executionWorkspaceRepositoryAccess).values({ companyId: f.companyId, rootKey: createHash("sha256").update(await realpath(sibling)).digest("hex"), + canonicalCwd: await realpath(sibling), kind: "project_source", scopeId: project.id }); + await db.insert(executionWorkspaceRepositoryRoots).values({ companyId: f.companyId, rootKey: createHash("sha256").update(await realpath(sibling)).digest("hex"), + canonicalCwd: await realpath(sibling), mode: "anonymous_only" }); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + await expect(f.request({ kind: "catalog", id: "private-repo" })).resolves.toMatchObject({ kind: "requires_next_admission" }); + }); + + it.each([ + { mode: "managed", direction: "ancestor" }, { mode: "managed", direction: "descendant" }, + { mode: "anonymous_only", direction: "ancestor" }, { mode: "anonymous_only", direction: "descendant" }, + ] as const)("fences $mode root policy against opposite-mode $direction acquisition", async ({ mode, direction }) => { + const f = await fixture(); + const child = path.join(f.admission.cwd, "nested"); await mkdir(child); + const existingRoot = direction === "ancestor" ? child : f.admission.cwd; + if (direction === "descendant") { + f.admission.cwd = child; + await db.update(executionWorkspaces).set({ cwd: child }).where(eq(executionWorkspaces.id, f.workspace.id)); + } + const canonicalCwd = await realpath(existingRoot); + await db.insert(executionWorkspaceRepositoryRoots).values({ companyId: f.companyId, canonicalCwd, + rootKey: createHash("sha256").update(canonicalCwd).digest("hex"), mode }); + if (mode === "managed") { + await db.update(heartbeatRuns).set({ contextSnapshot: { issueId: f.issueId, executionPolicy: f.policy } }).where(eq(heartbeatRuns.id, f.runId)); + await expect(f.service.prepareForAdmission(f.admission)).rejects.toThrow(/anonymous public URLs only/); + } else await expect(f.request({ kind: "catalog", id: "private-repo" })).rejects.toThrow(/anonymous public URLs only/); + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it("reauthorizes a nested private publication against an already-admitted parent reader", async () => { + const f = await fixture(); + await f.service.prepareForAdmission(f.admission); + const peer = await standardPeer(f), child = path.join(f.admission.cwd, "private"); await mkdir(child); + peer.admission.cwd = child; + await db.update(executionWorkspaces).set({ cwd: child }).where(eq(executionWorkspaces.id, peer.workspace.id)); + await db.update(issues).set({ visibility: "private", privacyRootIssueId: peer.issueId, responsibleUserId: peer.userId }).where(eq(issues.id, peer.issueId)); + const request = () => f.service.request({ companyId: f.companyId, issueId: peer.issueId, actor: peer.actor, + request: { repository: { kind: "url", url: "https://github.com/public/nested" }, requestKey: randomUUID() } }); + await expect(request()).rejects.toThrow(/workspace access|authorization boundary/); + await db.insert(issueAccessGrants).values([ + { issueId: peer.issueId, subjectType: "agent", subjectId: f.agentId, source: "explicit", grantedByUserId: peer.userId }, + { issueId: peer.issueId, subjectType: "user", subjectId: f.userId, source: "explicit", grantedByUserId: peer.userId }, + ]); + const receipt = await request(); + await localPrivateClone({ ...f, issueId: peer.issueId }, async () => { + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, peer.issueId)); + }); + await expect(f.service.prepareForAdmission(peer.admission)).rejects.toThrow(/workspace access|authorization boundary/); + await expect(fs.lstat(path.join(child, receipt.repository.relativePath))).rejects.toMatchObject({ code: "ENOENT" }); + expect(acquisition.materialize).toHaveBeenCalledOnce(); + }); + + it.each(["subdirectory", "linked_worktree", "nested_target"] as const)("retains private Git owner provenance copied through a %s without requiring source write grants", async sourceKind => { + const f = await fixture(), exec = promisify(execFileCallback); + const privateCwd = path.join(f.admission.cwd, "private"), publicCwd = path.join(f.admission.cwd, "public"); + await mkdir(privateCwd); await mkdir(publicCwd); + await writeFile(path.join(privateCwd, "secret.txt"), "Copied private Git bytes\n"); + await writeFile(path.join(publicCwd, "readme.txt"), "Public nested configuration\n"); + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private copied source", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private nested files", cwd: privateCwd }); + if (sourceKind === "nested_target") { + const [outer] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected ancestor source", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: outer.id, name: "Ancestor source", cwd: f.admission.cwd }); + } + let sourceCwd = publicCwd; + if (sourceKind === "linked_worktree") { + sourceCwd = path.join(root, `${f.issueId}-linked`); + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "linked", sourceCwd]); + } + await expect(assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd, actor: f.actor })).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd, actor: f.actor }); + expect(captured.projectIds).toContain(project.id); + const targetCwd = sourceKind === "nested_target" ? path.join(f.admission.cwd, ".paperclip", "worktrees", "copy") : path.join(root, `${f.issueId}-copy`); + await retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd, targetCwd, actor: f.actor, captured }); + await expect(fs.lstat(targetCwd)).rejects.toMatchObject({ code: "ENOENT" }); + if (sourceKind === "nested_target") { + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId: f.companyId, cwd: targetCwd, + actor: f.actor, issueId: f.issueId })).rejects.toThrow(/protected|assignment grant/); + } + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "copy", targetCwd]); + await retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd, targetCwd, actor: f.actor, captured, phase: "verified" }); + const [target] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Isolated copy", cwd: targetCwd, + mode: "isolated_workspace", strategyType: "git_worktree" }).returning(); + await db.update(issues).set({ executionWorkspaceId: target.id }).where(eq(issues.id, f.issueId)); + await expect(f.service.prepareForAdmission({ ...f.admission, workspaceId: target.id, cwd: targetCwd })).resolves.toEqual([]); + expect(await readFile(path.join(targetCwd, "private", "secret.txt"), "utf8")).toBe("Copied private Git bytes\n"); + if (sourceKind === "nested_target") { + await fs.rename(path.join(targetCwd, ".git"), path.join(targetCwd, ".saved-git")); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId: f.companyId, cwd: targetCwd, + actor: f.actor, issueId: f.issueId })).rejects.toThrow(/protected|assignment grant/); + await fs.rename(path.join(targetCwd, ".saved-git"), path.join(targetCwd, ".git")); + } + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: f.actor, sourceWorkspaceId: target.id })).rejects.toThrow(/source project/); + }); + + it.each(["file_uri", "absolute_path", "materialized_copy"] as const)("authorizes URL-only private configured owners before a public local clone (%s)", async transport => { + const f = await fixture(), exec = promisify(execFileCallback); + const sourceCwd = path.join(root, `${f.issueId}-url-only-source`); await mkdir(sourceCwd); + await exec("git", ["init", "--initial-branch=main", sourceCwd]); + await writeFile(path.join(sourceCwd, "secret.txt"), "Private URL source\n"); + await exec("git", ["-C", sourceCwd, "add", "."]); + await exec("git", ["-C", sourceCwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const repoUrl = transport === "absolute_path" ? sourceCwd : `file://${sourceCwd}`; + const [privateProject] = await db.insert(projects).values({ companyId: f.companyId, name: "Private URL owner", visibility: "private" }).returning(); + const privateCopy = transport === "materialized_copy" ? path.join(root, `${f.issueId}-independent-private`) : null; + if (privateCopy) await exec("git", ["clone", repoUrl, privateCopy]); + const [privateSource] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: privateProject.id, + name: "URL-only private source", cwd: privateCopy, repoUrl }).returning(); + const [publicProject] = await db.insert(projects).values({ companyId: f.companyId, name: "Public URL selection" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: publicProject.id, name: "Same URL", cwd: null, repoUrl }); + await db.update(issues).set({ projectId: publicProject.id }).where(eq(issues.id, f.issueId)); + const sourceInput = { companyId: f.companyId, sourceCwd, actor: f.actor, copyKind: "repository" as const }; + if (privateCopy) { + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, sourceInput); + expect(captured.projectIds).not.toContain(privateProject.id); + return; + } + await expect(assertIsolatedWorkspaceSourceReadAccess(db, sourceInput)).rejects.toThrow(/source project/); + const before = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(before).toEqual([]); + await projectService(db, { captureWorkspaceUpdates: false }).updateWorkspace(privateProject.id, privateSource.id, + { repoUrl: "https://github.com/example/moved" }); + await expect(assertIsolatedWorkspaceSourceReadAccess(db, sourceInput)).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: privateProject.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: privateProject.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, sourceInput); + const targetCwd = path.join(root, `${f.issueId}-url-copy`); + await retainIsolatedWorkspaceSourceAccess(db, { ...sourceInput, targetCwd, captured, issueId: f.issueId }); + await exec("git", ["clone", repoUrl, targetCwd]); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, privateProject.id)); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: f.actor })).rejects.toThrow(/source project/); + expect(await readFile(path.join(targetCwd, "secret.txt"), "utf8")).toBe("Private URL source\n"); + }); + + it.each(["checkout", "bare", "protected_ancestor"] as const)("guards unbound local repository staging and publication without worktree authority (%s)", async scenario => { + const f = await fixture(), exec = promisify(execFileCallback); + const checkout = path.join(root, `${f.issueId}-local-origin`); await mkdir(checkout); + await exec("git", ["init", "--initial-branch=main", checkout]); + await writeFile(path.join(checkout, "secret.txt"), "Committed private bytes\n"); + await exec("git", ["-C", checkout, "add", "."]); + await exec("git", ["-C", checkout, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "private source"]); + const sourceCwd = scenario === "bare" ? path.join(root, `${f.issueId}-bare.git`) : checkout; + if (scenario === "bare") await exec("git", ["clone", "--bare", checkout, sourceCwd]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private local origin", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Local source owner", cwd: sourceCwd }); + const sourceInput = { companyId: f.companyId, sourceCwd, actor: f.actor, copyKind: "repository" as const }; + await expect(assertIsolatedWorkspaceSourceReadAccess(db, sourceInput)).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, sourceInput); + const stage = scenario === "protected_ancestor" ? path.join(sourceCwd, "clone-staging") : path.join(root, `${f.issueId}-staging`); + const input = { ...sourceInput, targetCwd: stage, captured, issueId: f.issueId, phase: "verified" as const }; + if (scenario === "protected_ancestor") { + await expect(retainIsolatedWorkspaceSourceAccess(db, input)).rejects.toThrow(/protected|assignment grant/); + await expect(fs.lstat(stage)).rejects.toMatchObject({ code: "ENOENT" }); + } else { + await retainIsolatedWorkspaceSourceAccess(db, input); + await exec("git", ["clone", sourceCwd, stage]); + const target = path.join(root, `${f.issueId}-published-local`); + await retainIsolatedWorkspaceSourceAccess(db, { ...input, targetCwd: target }); + await fs.rename(stage, target); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + await expect(retainIsolatedWorkspaceSourceAccess(db, { ...input, targetCwd: target })).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: target, actor: f.actor })).rejects.toThrow(/source project/); + expect(await readFile(path.join(target, "secret.txt"), "utf8")).toBe("Committed private bytes\n"); + } + const entries = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(entries.some(entry => entry.kind === "isolated_project_source" && entry.scopeId === project.id)).toBe(true); + expect(entries.some(entry => entry.kind === "isolated_copy")).toBe(false); + }); + + it.each(["url", "catalog"] as const)("rejects independent local-file acquisition through %s before a receipt or clone", async kind => { + const f = await fixture(); + const url = `file://${f.admission.cwd}`; + if (kind === "catalog") acquisition.catalog.mockResolvedValue({ repositories: [{ id: "private-repo", fullName: "private/local", url, connections: ["fixture"] }] }); + await expect(f.request(kind === "url" ? { kind, url } : { kind, id: "private-repo" })).rejects.toThrow(/HTTPS GitHub/); + expect(await f.service.list(f.companyId, f.workspace.id)).toEqual([]); + expect(acquisition.materialize).not.toHaveBeenCalled(); + }); + + it.each(["grant_revoked", "source_detached", "retained_reader"] as const)("retains configured sibling source privacy on the task root (%s)", async scenario => { + const f = await fixture(), exec = promisify(execFileCallback); + const sourceCwd = path.join(root, `${f.issueId}-configured-source`); + await mkdir(sourceCwd); + const privateCwd = path.join(sourceCwd, "private"); await mkdir(privateCwd); + await writeFile(path.join(privateCwd, "secret.txt"), "Private configured bytes\n"); + await exec("git", ["init", "--initial-branch=main", sourceCwd]); + await exec("git", ["-C", sourceCwd, "add", "."]); + await exec("git", ["-C", sourceCwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private sibling source", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + const [configuration] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private subtree", cwd: privateCwd }).returning(); + const input = { ...f.admission, sourceCwd }; + const peer = scenario === "retained_reader" ? await standardPeer(f) : null; + if (peer) await f.service.prepareForAdmission(peer.admission); + await expect(f.service.prepareConfiguredRepositorySource(input)).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + if (peer) { + await expect(f.service.prepareConfiguredRepositorySource(input)).rejects.toThrow(/source project/); + } else { + const beforePublish = await f.service.prepareConfiguredRepositorySource(input); + const destination = path.join(f.admission.cwd, ".paperclip-repositories", "configured-source"); + await exec("git", ["clone", sourceCwd, destination]); + await expect(beforePublish()).resolves.toBeUndefined(); + if (scenario === "source_detached") await db.delete(projectWorkspaces).where(eq(projectWorkspaces.id, configuration.id)); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + await expect(beforePublish()).rejects.toThrow(/source project/); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: f.admission.cwd, actor: f.actor })).rejects.toThrow(/source project/); + expect(await readFile(path.join(destination, "private", "secret.txt"), "utf8")).toBe("Private configured bytes\n"); + } + const entries = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(entries.some(entry => entry.kind === "isolated_project_source" && entry.scopeId === project.id)).toBe(true); + expect(entries.some(entry => entry.kind === "isolated_copy")).toBe(false); + expect(entries.filter(entry => entry.kind === "reader")).toHaveLength(peer ? 1 : 0); + }); + + it.each(["primary", "linked", "separate primary"] as const)("does not probe unrelated company Git paths when the %s source is identified", async kind => { + const f = await fixture(), exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", + ...(kind === "separate primary" ? ["--separate-git-dir", path.join(root, `${f.issueId}-metadata`)] : []), f.admission.cwd]); + const privateCwd = path.join(f.admission.cwd, "private"); await mkdir(privateCwd); + await writeFile(path.join(privateCwd, "secret.txt"), "Private tracked bytes\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + let sourceCwd = f.admission.cwd; + if (kind === "linked") { + sourceCwd = path.join(root, `${f.issueId}-linked-normal`); + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "linked-normal", sourceCwd]); + } + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private nested owner", visibility: "private" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Nested source", cwd: privateCwd }); + const unrelated = path.join(root, `${f.issueId}-unrelated-candidate`); await mkdir(unrelated); + await db.insert(executionWorkspaces).values({ companyId: f.companyId, name: "Unrelated task files", cwd: unrelated, + mode: "shared_workspace", strategyType: "operator_directory" }); + const wrapper = path.join(root, `${f.issueId}-git-bin`); await mkdir(wrapper); + const probeLog = path.join(wrapper, "probed.txt"); + await writeFile(probeLog, ""); + const realGit = (await exec("sh", ["-c", "command -v git"])).stdout.trim(); + await writeFile(path.join(wrapper, "git"), `#!/bin/sh +if [ "$1" = "-C" ] && [ "$2" = "$PROBE_UNRELATED" ]; then + printf 'unexpected Git probe\\n' >> "$PROBE_LOG" + exit 78 +fi +exec "$PROBE_REAL_GIT" "$@" +`, { mode: 0o755 }); + const previous = { PATH: process.env.PATH, PROBE_UNRELATED: process.env.PROBE_UNRELATED, + PROBE_LOG: process.env.PROBE_LOG, PROBE_REAL_GIT: process.env.PROBE_REAL_GIT }; + try { + vi.stubEnv("PATH", `${wrapper}${path.delimiter}${process.env.PATH}`); + vi.stubEnv("PROBE_UNRELATED", await realpath(unrelated)); + vi.stubEnv("PROBE_LOG", probeLog); vi.stubEnv("PROBE_REAL_GIT", realGit); + const input = { companyId: f.companyId, sourceCwd, actor: f.actor }; + await expect(assertIsolatedWorkspaceSourceReadAccess(db, input)).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, input); + expect(captured.projectIds).toContain(project.id); + expect(await readFile(probeLog, "utf8")).toBe(""); + } finally { + for (const [name, value] of Object.entries(previous)) vi.stubEnv(name, value); + } + }); + + it.each(["unrelated", "metadata", "checkout", "linked_main", "linked_retained"] as const)("authorizes actual separate Git storage without inferring its parent (%s)", async owner => { + const f = await fixture(), exec = promisify(execFileCallback); + const storageParent = path.join(root, `${f.issueId}-storage`); + const metadata = path.join(storageParent, "metadata"), unrelated = path.join(storageParent, "private-task"); + await mkdir(unrelated, { recursive: true }); + await exec("git", ["init", "--initial-branch=main", "--separate-git-dir", metadata, f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "source.txt"), "Source bytes\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + let sourceCwd = f.admission.cwd; + if (owner === "linked_main" || owner === "linked_retained") { + sourceCwd = path.join(root, `${f.issueId}-linked-source`); + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "source-linked", sourceCwd]); + } + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Private actual owner", visibility: "private" }).returning(); + const [configuration] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private source", + cwd: owner === "metadata" ? metadata : owner === "unrelated" ? unrelated : f.admission.cwd }).returning(); + if (owner === "linked_retained") { + const taskRoot = path.join(root, `${f.issueId}-different-task-root`); + await mkdir(taskRoot); + await db.update(executionWorkspaces).set({ cwd: taskRoot }).where(eq(executionWorkspaces.id, f.workspace.id)); + await projectService(db).removeWorkspace(project.id, configuration.id); + } + const input = { companyId: f.companyId, sourceCwd, actor: f.actor, copyKind: "repository" as const }; + if (owner !== "unrelated") { + await expect(assertIsolatedWorkspaceSourceReadAccess(db, input)).rejects.toThrow(/source project/); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + } + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, input); + expect(captured.roots).toContain(await realpath(metadata)); + expect(captured.roots).toContain(await realpath(f.admission.cwd)); + expect(captured.roots).not.toContain(await realpath(storageParent)); + expect(captured.projectIds.includes(project.id)).toBe(owner !== "unrelated"); + }); + + it.each(["deleted_primary", "retained_reader"] as const)("retains separate metadata privacy across %s", async scenario => { + const f = await fixture(), exec = promisify(execFileCallback); + const storageParent = path.join(root, `${f.issueId}-deleted-primary-storage`); + const metadata = path.join(storageParent, "metadata"), linked = path.join(root, `${f.issueId}-surviving-linked`); + await mkdir(storageParent); + await exec("git", ["init", "--initial-branch=main", "--separate-git-dir", metadata, f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "private.txt"), "PRIVATE_PRIMARY_BYTES"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "private source"]); + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "surviving-linked", linked]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Deleted private primary", visibility: "private" }).returning(); + const [configuration] = await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private source", cwd: f.admission.cwd }).returning(); + const input = { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor, copyKind: "repository" as const }; + await expect(assertIsolatedWorkspaceSourceReadAccess(db, input)).rejects.toThrow(/source project/); + expect(await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId))).toEqual([]); + const peer = scenario === "retained_reader" ? await standardPeer(f) : null; + if (peer) { + await db.update(executionWorkspaces).set({ cwd: metadata }).where(eq(executionWorkspaces.id, peer.workspace.id)); + await f.service.prepareForAdmission({ ...peer.admission, cwd: metadata }); + } + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + if (peer) { + await expect(assertIsolatedWorkspaceSourceReadAccess(db, input)).rejects.toThrow(/source project/); + const before = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(before.some(entry => entry.kind === "project_source" && entry.scopeId === project.id)).toBe(false); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: peer.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: peer.userId }, + ]); + } + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, input); + expect(captured.projectIds).toContain(project.id); + const metadataRows = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(metadataRows).toContainEqual(expect.objectContaining({ canonicalCwd: await realpath(metadata), kind: "project_source", scopeId: project.id })); + expect(metadataRows.some(entry => entry.kind === "isolated_copy")).toBe(false); + if (peer) return; + const firstCopy = path.join(root, `${f.issueId}-authorized-copy`); + await retainIsolatedWorkspaceSourceAccess(db, { ...input, captured, targetCwd: firstCopy, issueId: f.issueId }); + await exec("git", ["clone", "--no-hardlinks", f.admission.cwd, firstCopy]); + await projectService(db).removeWorkspace(project.id, configuration.id); + await rm(f.admission.cwd, { recursive: true, force: true }); + await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + expect(await readFile(path.join(linked, "private.txt"), "utf8")).toBe("PRIVATE_PRIMARY_BYTES"); + await expect(assertIsolatedWorkspaceSourceReadAccess(db, { ...input, sourceCwd: linked })).rejects.toThrow(/source project/); + }); + + it.each(["ordinary", "linked"] as const)("requires real linked-worktree identity with separate Git metadata (%s)", async kind => { + const f = await fixture(), exec = promisify(execFileCallback); + const metadata = path.join(root, `${f.issueId}-metadata`); + await exec("git", ["init", "--initial-branch=main", "--separate-git-dir", metadata, f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "source.txt"), "Source bytes\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected separate checkout", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Source", cwd: f.admission.cwd }); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + const targetCwd = kind === "ordinary" ? f.admission.cwd : path.join(f.admission.cwd, "linked"); + if (kind === "linked") await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "linked", targetCwd]); + await writeFile(path.join(targetCwd, "source.txt"), "Preserved dirty edit\n"); + const retain = () => retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, + targetCwd, actor: f.actor, issueId: f.issueId, captured, phase: "verified" }); + if (kind === "ordinary") await expect(retain()).rejects.toThrow(/not a verified Git worktree|protected/); + else { + await expect(retain()).resolves.toBeUndefined(); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: f.actor, issueId: f.issueId })).resolves.toBeUndefined(); + } + const entries = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(entries.some(entry => entry.kind === "isolated_copy")).toBe(kind === "linked"); + expect(await readFile(path.join(targetCwd, "source.txt"), "utf8")).toBe("Preserved dirty edit\n"); + }); + + it.each(["upgrade", "interrupted_creation", "unrelated_owner"] as const)("validates a registered dirty worktree without completed copy proof after %s", async scenario => { + const f = await fixture(), exec = promisify(execFileCallback); + const git = async (...args: string[]) => (await exec("git", ["-C", f.admission.cwd, ...args])).stdout; + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "tracked.txt"), "Original source\n"); + await git("add", "."); + await git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected legacy source", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Source", cwd: f.admission.cwd }); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + const targetCwd = path.join(f.admission.cwd, ".paperclip", "worktrees", "legacy"); + const input = { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, actor: f.actor, issueId: f.issueId, captured }; + if (scenario === "interrupted_creation") await retainIsolatedWorkspaceSourceAccess(db, input); + if (scenario === "unrelated_owner") { + const unrelated = path.join(root, `${f.issueId}-unrelated-git`); + await exec("git", ["init", "--initial-branch=main", unrelated]); + await exec("git", ["-C", unrelated, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "--allow-empty", "-m", "unrelated"]); + await exec("git", ["-C", unrelated, "worktree", "add", "-b", "legacy", targetCwd]); + } else await git("worktree", "add", "-b", "legacy", targetCwd); + await writeFile(path.join(targetCwd, "tracked.txt"), "Keep dirty edit\n"); + await writeFile(path.join(targetCwd, "untracked.txt"), "Keep untracked work\n"); + const before = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(before.some(entry => entry.kind === "isolated_copy")).toBe(false); + const gitState = await exec("git", ["-C", targetCwd, "status", "--porcelain"]); + const head = await exec("git", ["-C", targetCwd, "rev-parse", "HEAD"]); + if (scenario === "unrelated_owner") { + await expect(retainIsolatedWorkspaceSourceAccess(db, input)).rejects.toThrow(/protected|assignment grant/); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: f.actor, issueId: f.issueId })).rejects.toThrow(/protected|assignment grant/); + } else { + await expect(retainIsolatedWorkspaceSourceAccess(db, input)).resolves.toBeUndefined(); + await expect(assertExecutionWorkspaceRootWriteAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: f.actor, issueId: f.issueId })).resolves.toBeUndefined(); + } + const after = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(after.some(entry => entry.kind === "isolated_copy" && entry.scopeId === project.id)).toBe(scenario !== "unrelated_owner"); + expect((await exec("git", ["-C", targetCwd, "status", "--porcelain"])).stdout).toBe(gitState.stdout); + expect((await exec("git", ["-C", targetCwd, "rev-parse", "HEAD"])).stdout).toBe(head.stdout); + expect(await readFile(path.join(targetCwd, "tracked.txt"), "utf8")).toBe("Keep dirty edit\n"); + expect(await readFile(path.join(targetCwd, "untracked.txt"), "utf8")).toBe("Keep untracked work\n"); + }); + + it("does not let an unmaterialized copy intent authorize writes to an existing source folder", async () => { + const f = await fixture(), exec = promisify(execFileCallback); + const targetCwd = path.join(f.admission.cwd, "tracked-private-folder"); + await mkdir(targetCwd); + await writeFile(path.join(targetCwd, "existing.txt"), "Protected original bytes\n"); + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected original source", visibility: "private", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Original files", cwd: f.admission.cwd }); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const writeInput = { companyId: f.companyId, cwd: targetCwd, actor: f.actor, issueId: f.issueId }; + await expect(assertExecutionWorkspaceRootWriteAccess(db, writeInput)).rejects.toThrow(/protected|assignment grant/); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + await expect(retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, + actor: f.actor, issueId: f.issueId, captured })).rejects.toThrow(/protected|assignment grant/); + await expect(fs.lstat(path.join(targetCwd, ".git"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await readFile(path.join(targetCwd, "existing.txt"), "utf8")).toBe("Protected original bytes\n"); + await expect(assertExecutionWorkspaceRootWriteAccess(db, writeInput)).rejects.toThrow(/protected|assignment grant/); + }); + + it("does not verify a missing or ordinary empty directory as an isolated copy", async () => { + const f = await fixture(), exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "source.txt"), "Source\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + const targetCwd = path.join(root, `${f.issueId}-never-materialized`); + const input = { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, actor: f.actor, captured, phase: "verified" as const }; + await expect(retainIsolatedWorkspaceSourceAccess(db, input)).rejects.toThrow(/not a verified Git worktree/); + await mkdir(targetCwd); + await expect(retainIsolatedWorkspaceSourceAccess(db, input)).rejects.toThrow(/not a verified Git worktree/); + const entries = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(entries.some(entry => entry.kind === "isolated_copy")).toBe(false); + }); + + it.each(["exact", "descendant"] as const)("requires write authority for a protected %s target before copying", async relation => { + const f = await fixture(), exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "source.txt"), "Source bytes\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const targetCwd = path.join(root, `${f.issueId}-protected-target`); + const protectedCwd = relation === "exact" ? targetCwd : path.join(targetCwd, "private"); + await mkdir(protectedCwd, { recursive: true }); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Protected target", + executionWorkspacePolicy: { authorizationPolicy: { assignmentPolicy: { mode: "protected" } } } }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Target owner", cwd: protectedCwd }); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + await expect(retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, + actor: f.actor, issueId: f.issueId, captured })).rejects.toThrow(/protected|assignment grant/); + await expect(fs.lstat(path.join(targetCwd, "source.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + const owners = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, f.companyId)); + expect(owners).toEqual(expect.arrayContaining([expect.objectContaining({ kind: "isolated_source", scopeId: f.workspace.id })])); + }); + + it("excludes available lexical descendants whose canonical roots are unrelated", async () => { + const f = await fixture(); + const unrelated = path.join(root, `${f.issueId}-unrelated`); await mkdir(unrelated); + const alias = path.join(f.admission.cwd, "link"); await symlink(unrelated, alias); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Unrelated private owner", visibility: "private" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Unrelated alias", cwd: alias }); + const [workspace] = await db.insert(executionWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Unrelated alias", cwd: alias, + mode: "shared_workspace", strategyType: "project_primary" }).returning(); + await db.insert(executionWorkspaceRepositories).values({ companyId: f.companyId, requestedByIssueId: f.issueId, executionWorkspaceId: workspace.id, + repositoryIdentity: "https://github.com/private/unrelated", repoUrl: "https://github.com/private/unrelated", relativePath: "unused", + requestedRef: "HEAD", requestKey: randomUUID() }); + await expect(f.service.prepareForAdmission(f.admission)).resolves.toEqual([]); + }); + + it.each(["revoked", "deleted_source", "deleted_owner", "retained_reader"] as const)("protects already-copied bytes when post-copy authorization fails (%s)", async failure => { + const f = await fixture(), exec = promisify(execFileCallback); + await exec("git", ["init", "--initial-branch=main", f.admission.cwd]); + await writeFile(path.join(f.admission.cwd, "secret.txt"), "Failed copy still private\n"); + await exec("git", ["-C", f.admission.cwd, "add", "."]); + await exec("git", ["-C", f.admission.cwd, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-m", "source"]); + const [project] = await db.insert(projects).values({ companyId: f.companyId, name: "Failed private copy", visibility: "private" }).returning(); + await db.insert(projectWorkspaces).values({ companyId: f.companyId, projectId: project.id, name: "Private source", cwd: f.admission.cwd }); + await db.insert(projectAccessMembers).values([ + { companyId: f.companyId, projectId: project.id, subjectType: "agent", subjectId: f.agentId }, + { companyId: f.companyId, projectId: project.id, subjectType: "user", subjectId: f.userId }, + ]); + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, actor: f.actor }); + const targetCwd = path.join(root, `${f.issueId}-failed-copy`); + if (failure === "retained_reader") { + await mkdir(targetCwd); + const peer = await standardPeer(f); + await db.update(executionWorkspaces).set({ cwd: targetCwd }).where(eq(executionWorkspaces.id, peer.workspace.id)); + await f.service.prepareForAdmission({ ...peer.admission, cwd: targetCwd }); + await expect(retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, + actor: f.actor, captured })).rejects.toThrow(/source project/); + await expect(fs.lstat(path.join(targetCwd, "secret.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + return; + } + await exec("git", ["-C", f.admission.cwd, "worktree", "add", "-b", "failed-copy", targetCwd]); + if (failure === "revoked") await db.delete(projectAccessMembers).where(eq(projectAccessMembers.projectId, project.id)); + if (failure === "deleted_owner") await db.delete(projects).where(eq(projects.id, project.id)); + if (failure === "deleted_source") await rm(f.admission.cwd, { recursive: true }); + await expect(retainIsolatedWorkspaceSourceAccess(db, { companyId: f.companyId, sourceCwd: f.admission.cwd, targetCwd, actor: f.actor, captured })).rejects.toThrow(); + const canonicalCwd = await realpath(targetCwd); + const owners = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.rootKey, createHash("sha256").update(canonicalCwd).digest("hex"))); + expect(owners).toEqual(expect.arrayContaining([expect.objectContaining({ kind: "isolated_project_source", scopeId: project.id })])); + const peer = await standardPeer(f); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: f.companyId, cwd: targetCwd, actor: peer.actor })).rejects.toThrow(/source project/); + expect(await readFile(path.join(targetCwd, "secret.txt"), "utf8")).toBe("Failed copy still private\n"); + }); + + +}); diff --git a/server/src/__tests__/task-workspace-tools.test.ts b/server/src/__tests__/task-workspace-tools.test.ts new file mode 100644 index 00000000000..40123b8df48 --- /dev/null +++ b/server/src/__tests__/task-workspace-tools.test.ts @@ -0,0 +1,29 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { callTaskWorkspaceTool } from "../services/task-workspace-tools.js"; + +afterEach(() => vi.unstubAllGlobals()); +const base = { apiUrl: "https://paperclip.example/api", token: "run-token", companyId: "company", issueId: "task" }; +describe("task workspace semantic transport", () => { + it("discovers task-selectable workspaces instead of legacy isolated reuse candidates", async () => { + const fetcher = vi.fn(async () => Response.json([{ id: "shared", mode: "shared_workspace" }])); + vi.stubGlobal("fetch", fetcher); + await expect(callTaskWorkspaceTool({ ...base, name: "list_workspaces", arguments: {} })).resolves.toEqual([{ id: "shared", mode: "shared_workspace" }]); + expect(fetcher.mock.calls[0]).toEqual(["https://paperclip.example/api/companies/company/execution-workspaces?summary=true&selectableForTask=true", expect.objectContaining({ method: "GET" })]); + }); + it("uses the normal bound task routes without passing caller identities or interrupt requests", async () => { + const fetcher = vi.fn(async () => Response.json({ kind: "scheduled", applies: "next_normal_admission" })); + vi.stubGlobal("fetch", fetcher); + await expect(callTaskWorkspaceTool({ ...base, name: "select_workspace", arguments: { selection: { kind: "task_directory" }, expectedBindingRevision: 3, requestKey: "move-1" } })).resolves.toMatchObject({ applies: "next_normal_admission" }); + expect(fetcher).toHaveBeenCalledTimes(1); + expect(fetcher.mock.calls[0]).toEqual(["https://paperclip.example/api/issues/task/workspace", expect.objectContaining({ method: "PUT", body: JSON.stringify({ selection: { kind: "task_directory" }, expectedBindingRevision: 3, requestKey: "move-1" }) })]); + }); + it("prepares a repository without creating a project or choosing a host path", async () => { + const fetcher = vi.fn(async () => Response.json({ kind: "requires_next_admission" })); + vi.stubGlobal("fetch", fetcher); + const args = { repository: { kind: "catalog", id: "repo" }, requestKey: "acquire-1" }; + await callTaskWorkspaceTool({ ...base, name: "prepare_repository", arguments: args }); + expect(fetcher.mock.calls[0]).toEqual(["https://paperclip.example/api/issues/task/workspace/repositories", expect.objectContaining({ method: "POST", body: JSON.stringify(args) })]); + await expect(callTaskWorkspaceTool({ ...base, name: "prepare_repository", arguments: { ...args, cwd: "/home" } })).rejects.toThrow(); + expect(fetcher).toHaveBeenCalledTimes(1); + }); +}); diff --git a/server/src/__tests__/trust-preset-resolver.test.ts b/server/src/__tests__/trust-preset-resolver.test.ts index 5aa206ed83c..d7ffd6398ab 100644 --- a/server/src/__tests__/trust-preset-resolver.test.ts +++ b/server/src/__tests__/trust-preset-resolver.test.ts @@ -53,6 +53,19 @@ describe("resolveCoreTrustPreset", () => { }); }); + it("intersects organizational and workspace-source project restrictions", () => { + const organization = { companyId, executionWorkspacePolicy: boundaryPolicy(lowTrustBoundary({ projectIds: [projectA, projectB], allowedToolClasses: ["git.read", "tests.local"] })) }; + const source = { companyId, executionWorkspacePolicy: boundaryPolicy(lowTrustBoundary({ projectIds: [projectB, projectC], allowedToolClasses: ["git.read"] })) }; + expect(resolveCoreTrustPreset({ companyId, project: organization })).toMatchObject({ kind: "low_trust_review" }); + expect(resolveCoreTrustPreset({ companyId, workspaceSourceProject: source })).toMatchObject({ kind: "low_trust_review" }); + expect(resolveCoreTrustPreset({ companyId, project: organization, workspaceSourceProject: source })).toMatchObject({ + kind: "low_trust_review", boundary: { projectIds: [projectB], allowedToolClasses: ["git.read"] }, + }); + expect(resolveCoreTrustPreset({ companyId, project: organization, + workspaceSourceProject: { companyId, executionWorkspacePolicy: boundaryPolicy(lowTrustBoundary({ projectIds: [projectC] })) }, + })).toMatchObject({ kind: "denied", reason: "missing_low_trust_boundary_scope" }); + }); + it("intersects low-trust agent, project, and issue policy boundaries", () => { const result = resolveCoreTrustPreset({ companyId, diff --git a/server/src/__tests__/workspace-host-provision.test.ts b/server/src/__tests__/workspace-host-provision.test.ts new file mode 100644 index 00000000000..b1065f8817b --- /dev/null +++ b/server/src/__tests__/workspace-host-provision.test.ts @@ -0,0 +1,68 @@ +import { projects, type Db } from "@paperclipai/db"; +import { describe, expect, it, vi } from "vitest"; +import { resolveProjectDefaultWorkspaceId } from "../services/project-default-workspace.js"; +import { workspaceRequiresCopyOnlyProvisioning } from "../services/workspace-host-provision.js"; + +describe("controller provision source classification", () => { + function fixture(rows: Array<{ id: string }> = [{ id: "default-source" }], defaultId?: string, existingRows = rows) { + const db = { select: vi.fn(() => { + let result: unknown[] = []; + const query = { from: vi.fn((table) => { + result = table === projects ? [{ executionWorkspacePolicy: defaultId ? { defaultProjectWorkspaceId: defaultId } : null }] + : defaultId ? existingRows : rows; + return query; + }), where: vi.fn(), orderBy: vi.fn(), limit: vi.fn(async () => result), + then: (resolve: (value: unknown[]) => unknown) => Promise.resolve(result).then(resolve) }; + query.where.mockReturnValue(query); + query.orderBy.mockReturnValue(query); + return query; + }) } as unknown as Db; + const input = { companyId: "company", task: { id: "task", projectId: "project" }, + sourceProjectId: "project", sourceWorkspaceId: "default-source" }; + return { db, input }; + } + + it.each(["explicit", "channel", "parent", "legacy"] as const)("keeps %s source choice copy-only even on the organizational default", async source => { + const { db, input } = fixture(); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, task: { ...input.task, + workspaceSelection: { version: 1, source, selection: { kind: "configured_source", projectWorkspaceId: "default-source", mode: "managed_isolated" } } } })).toBe(true); + }); + + it("preserves the implicit organizational default and server-generated operator cwd", async () => { + const { db, input } = fixture(); + expect(await workspaceRequiresCopyOnlyProvisioning(db, input)).toBe(false); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, sourceProjectId: null, sourceWorkspaceId: null, + task: { ...input.task, workspaceSelection: { version: 1, source: "operator_cwd", selection: { kind: "task_directory" } } } })).toBe(false); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, task: null, sourceProjectId: null, sourceWorkspaceId: null })).toBe(false); + }); + + it("preserves copy-only lineage after intent clearing and source ownership changes", async () => { + const { db, input } = fixture(); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, boundWorkspace: { metadata: { _controllerProvisionCopyOnly: true } } })).toBe(true); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, boundWorkspace: { sourceIssueId: "parent-task" } })).toBe(true); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, task: { ...input.task, projectId: null } })).toBe(true); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, sourceWorkspaceId: null })).toBe(true); + }); + + it("does not upgrade missing or fallback source identities to a default approval", async () => { + const { db, input } = fixture([{ id: "earlier-unusable-source" }]); + expect(await workspaceRequiresCopyOnlyProvisioning(db, input)).toBe(true); + expect(await workspaceRequiresCopyOnlyProvisioning(fixture([]).db, input)).toBe(true); + }); + it("uses configured defaults before primary fallback without resolving a foreign or missing pointer into approval", async () => { + const { db, input } = fixture([{ id: "older-source" }], "default-source", [{ id: "default-source" }]); + expect(await workspaceRequiresCopyOnlyProvisioning(db, input)).toBe(false); + const missing = fixture([{ id: "older-source" }], "default-source", []); + expect(await workspaceRequiresCopyOnlyProvisioning(missing.db, missing.input)).toBe(true); + const foreign = fixture([{ id: "older-source" }], "foreign-source"); + expect(await resolveProjectDefaultWorkspaceId(foreign.db, { companyId: "company", projectId: "project" })).toBe("foreign-source"); + expect(await workspaceRequiresCopyOnlyProvisioning(foreign.db, { ...foreign.input, sourceProjectId: "foreign-project", sourceWorkspaceId: "foreign-source" })).toBe(true); + }); + + it("retains explicit and sticky restrictions when the configured default matches the source", async () => { + const { db, input } = fixture([], "default-source", [{ id: "default-source" }]); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, boundWorkspace: { metadata: { _controllerProvisionCopyOnly: true } } })).toBe(true); + expect(await workspaceRequiresCopyOnlyProvisioning(db, { ...input, task: { ...input.task, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "configured_source", projectWorkspaceId: "default-source", mode: "managed_isolated" } } } })).toBe(true); + }); +}); diff --git a/server/src/__tests__/workspace-runtime.test.ts b/server/src/__tests__/workspace-runtime.test.ts index 418c9dad8df..0d6da1071f3 100644 --- a/server/src/__tests__/workspace-runtime.test.ts +++ b/server/src/__tests__/workspace-runtime.test.ts @@ -794,6 +794,41 @@ describe("ensureServerWorkspaceLinksCurrent", () => { }); describe("realizeExecutionWorkspace", () => { + it.each(["primary", "linked"] as const)("realizes from a %s checkout with separate Git metadata without adopting its unrelated parent", async sourceKind => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-separate-owner-")); + try { + const wrongOwner = path.join(root, "wrong-owner"), primary = path.join(root, "source"); + const common = path.join(wrongOwner, "separate-metadata"); + await fs.mkdir(wrongOwner); + await runGit(wrongOwner, ["init", "-b", "main"]); + await runGit(wrongOwner, ["config", "user.email", "test@example.test"]); + await runGit(wrongOwner, ["config", "user.name", "Test"]); + await fs.writeFile(path.join(wrongOwner, "wrong.txt"), "UNRELATED_PARENT_BYTES"); + await runGit(wrongOwner, ["add", "."]); + await runGit(wrongOwner, ["commit", "-m", "unrelated"]); + await execFileAsync("git", ["init", "-b", "main", "--separate-git-dir", common, primary]); + await runGit(primary, ["config", "user.email", "test@example.test"]); + await runGit(primary, ["config", "user.name", "Test"]); + await fs.writeFile(path.join(primary, "source.txt"), "AUTHORIZED_SOURCE_BYTES"); + await runGit(primary, ["add", "."]); + await runGit(primary, ["commit", "-m", "source"]); + const source = sourceKind === "primary" ? primary : path.join(root, "linked-source"); + if (sourceKind === "linked") await runGit(primary, ["worktree", "add", "-b", "linked-source", source]); + const wrongHead = await readGit(wrongOwner, ["rev-parse", "HEAD"]); + const expectedHead = await readGit(source, ["rev-parse", "HEAD"]); + const workspace = await realizeWorktreeForTest(source, "HEAD"); + expect(await readGit(workspace.cwd, ["rev-parse", "HEAD"])).toBe(expectedHead); + expect(await fs.readFile(path.join(workspace.cwd, "source.txt"), "utf8")).toBe("AUTHORIZED_SOURCE_BYTES"); + await expect(fs.stat(path.join(workspace.cwd, "wrong.txt"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(await readGit(wrongOwner, ["rev-parse", "HEAD"])).toBe(wrongHead); + expect(await readGit(wrongOwner, ["branch", "--list", workspace.branchName!])).toBe(""); + expect(await fs.realpath(path.resolve(workspace.cwd, await readGit(workspace.cwd, ["rev-parse", "--git-common-dir"])))).toBe(await fs.realpath(common)); + const reused = await realizeWorktreeForTest(source, "HEAD"); + expect(reused.cwd).toBe(workspace.cwd); + expect(reused.created).toBe(false); + } finally { await fs.rm(root, { recursive: true, force: true }); } + }); + it("defaults new git worktrees to freshly fetched origin/master", async () => { const sourceRepo = await createTempRepo("master"); const remoteDir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-worktree-remote-")); @@ -1707,6 +1742,47 @@ process.stderr.write(${JSON.stringify(stderr)}, () => { process.exitCode = ${cod expect(path.basename(realized.cwd)).toBe("PAP-992.hotfix-april-1"); }); + it.each(["configured", "builtin"] as const)("skips %s host provisioning for fresh, reused and restored sandbox worktrees", async kind => { + const repoRoot = await createTempRepo(); + const marker = path.join(repoRoot, "host-provision-ran"); + await fs.mkdir(path.join(repoRoot, "scripts")); + const script = kind === "configured" ? "custom.sh" : "provision-worktree.sh"; + await fs.writeFile(path.join(repoRoot, "scripts", script), + `#!/bin/sh\nprintf 'host execution\n' > '${marker}'\n`); + await runGit(repoRoot, ["add", "scripts"]); + await runGit(repoRoot, ["commit", "-m", "Repository-controlled provision script"]); + const command = kind === "configured" ? `bash ./scripts/${script}` : undefined; + const input = { + base: { baseCwd: repoRoot, source: "project_primary" as const, projectId: "project-1", + workspaceId: "source-1", repoUrl: null, repoRef: "HEAD" }, + config: { workspaceStrategy: { type: "git_worktree", baseRef: "main", provisionCommand: command } }, + issue: { id: "issue-sandbox", identifier: "PAP-449", title: "Sandbox files" }, + agent: { id: "agent-1", name: "Sandbox worker", companyId: "company-1" }, + skipHostProvision: true, + }; + const fresh = await realizeExecutionWorkspace(input); + expect(fresh.created).toBe(true); + expect(existsSync(marker)).toBe(false); + const reused = await realizeExecutionWorkspace(input); + expect(reused.cwd).toBe(fresh.cwd); + expect(reused.created).toBe(false); + expect(existsSync(marker)).toBe(false); + const restoreInput = { base: input.base, issue: input.issue, agent: input.agent, skipHostProvision: true, + workspace: { mode: "isolated_workspace", strategyType: "git_worktree", cwd: fresh.cwd, + providerRef: fresh.cwd, projectId: "project-1", projectWorkspaceId: "source-1", repoUrl: null, + baseRef: "main", branchName: fresh.branchName, config: { provisionCommand: command }, + metadata: RUNTIME_OWNED_GIT_BRANCH_METADATA } }; + expect((await ensurePersistedExecutionWorkspaceAvailable(restoreInput))?.cwd).toBe(fresh.cwd); + expect(existsSync(marker)).toBe(false); + await fs.rm(fresh.cwd, { recursive: true, force: true }); + expect(existsSync(fresh.cwd)).toBe(false); + expect((await ensurePersistedExecutionWorkspaceAvailable(restoreInput))?.cwd).toBe(fresh.cwd); + expect(existsSync(marker)).toBe(false); + await expect(fs.readFile(path.join(fresh.cwd, "README.md"), "utf8")).resolves.toBe("hello\n"); + await ensurePersistedExecutionWorkspaceAvailable({ ...restoreInput, skipHostProvision: undefined }); + await expect(fs.readFile(marker, "utf8")).resolves.toBe("host execution\n"); + }); + it("runs a configured provision command inside the derived worktree", async () => { const repoRoot = await createTempRepo(); await fs.mkdir(path.join(repoRoot, "scripts"), { recursive: true }); @@ -4238,6 +4314,34 @@ describe("ensureRuntimeServicesForRun", () => { }; } + it.each(["configured", "builtin"] as const)("suppresses %s runtime provision scripts for remote runs without disabling local provisioning", async kind => { + const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-remote-runtime-provision-")); + const restorePaperclipEnv = configureRuntimeProvisionTestHome(workspaceRoot, "remote-runtime-provision"); + const marker = path.join(workspaceRoot, "runtime-script-ran"); + const command = `printf host-runtime > '${marker}'`; + await fs.mkdir(path.join(workspaceRoot, "scripts")); + await fs.writeFile(path.join(workspaceRoot, "scripts", "provision-worktree-runtime.sh"), `${command}\n`); + const workspace = { ...buildWorkspace(workspaceRoot), strategy: "git_worktree" as const, worktreePath: workspaceRoot }; + const config = runtimeProvisionTestConfig({ provisionCommand: kind === "configured" ? command : undefined }); + const { recorder, operations } = createWorkspaceOperationRecorderDouble(); + const input = { runId: randomUUID(), agent: { id: "agent-1", name: "Remote worker", companyId: "company-1" }, + issue: null, workspace, config, executionWorkspaceId: randomUUID(), adapterEnv: {}, recorder }; + try { + expect(await ensureRuntimeServicesForRun({ ...input, skipHostProvision: true })).toHaveLength(1); + await expect(fs.readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + expect(operations.filter(operation => ["workspace_seed", "workspace_runtime_provision"].includes(operation.phase))).toHaveLength(0); + await stopRuntimeServicesForExecutionWorkspace({ executionWorkspaceId: input.executionWorkspaceId, workspaceCwd: workspaceRoot }); + const local = ensureRuntimeServicesForRun({ ...input, runId: randomUUID() }); + if (kind === "builtin") await expect(local).rejects.toThrow(/without a readable seed manifest/); + else expect(await local).toHaveLength(1); + expect(await fs.readFile(marker, "utf8")).toBe("host-runtime"); + } finally { + await stopRuntimeServicesForExecutionWorkspace({ executionWorkspaceId: input.executionWorkspaceId, workspaceCwd: workspaceRoot }); + await fs.rm(workspaceRoot, { recursive: true, force: true }); + restorePaperclipEnv(); + } + }); + it("runs runtime provisioning once when service starts race for the same workspace", async () => { const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-runtime-provision-race-")); const restorePaperclipEnv = configureRuntimeProvisionTestHome(workspaceRoot, "runtime-provision-race"); diff --git a/server/src/app.ts b/server/src/app.ts index 238a5bad698..5aa7c79b1db 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -29,6 +29,7 @@ import { publicMcpIngressRoutes, publicMcpManagementRoutes } from "./routes/publ import { agentAvatarRoutes } from "./routes/agent-avatars.js"; import { decisionModelRoutes } from "./routes/decision-models.js"; import { aiConnectionRoutes } from "./routes/ai-connections.js"; +import { taskWorkspaceRoutes } from "./routes/task-workspaces.js"; import { projectToolRoutes } from "./routes/project-tools.js"; import { emailChannelService } from "./services/email-channels.js"; import { emailRoutes, emailWebhookRoutes } from "./routes/email.js"; @@ -832,6 +833,7 @@ export async function createApp( api.use(assetRoutes(db, opts.storageService)); api.use(agentProfileAvatarRoutes(db, opts.storageService)); api.use(projectToolRoutes(db)); + api.use(taskWorkspaceRoutes(db)); api.use(projectRoutes(db)); api.use(caseRoutes(db, opts.storageService)); api.use(issueTreeControlRoutes(db, { pluginWorkerManager: workerManager })); diff --git a/server/src/redaction.ts b/server/src/redaction.ts index 31a7917a5d3..03efe939d53 100644 --- a/server/src/redaction.ts +++ b/server/src/redaction.ts @@ -341,7 +341,9 @@ const NATIVE_RUN_SPAN_NAMES = new Set([ "runner.turn.submit", "task.prepare", "task.run", + "task.provider_session", "task.run.measured", + "task.provider_session.measured", "task.settle", ]); const CLI_SECRET_FLAG_RE = new RegExp( diff --git a/server/src/routes/execution-workspaces.ts b/server/src/routes/execution-workspaces.ts index 280d9bc01ed..71a14a7f485 100644 --- a/server/src/routes/execution-workspaces.ts +++ b/server/src/routes/execution-workspaces.ts @@ -3,7 +3,7 @@ import { canActorReadExecutionWorkspace, executionWorkspaceReadSqlCondition } fr import { spawn } from "node:child_process"; import { accessSync, constants as fsConstants, existsSync, readFileSync } from "node:fs"; import path from "node:path"; -import { and, eq } from "drizzle-orm"; +import { and, eq, sql } from "drizzle-orm"; import { Router, type Request, type Response } from "express"; import type { Db } from "@paperclipai/db"; import { issues, projects, projectWorkspaces } from "@paperclipai/db"; @@ -137,6 +137,7 @@ export function executionWorkspaceRoutes(db: Db, opts: { pluginWorkerManager?: P issueId: req.query.issueId as string | undefined, status: req.query.status as string | undefined, reuseEligible: req.query.reuseEligible === "true", + selectableForTask: req.query.selectableForTask === "true", }; const workspaces = req.query.summary === "true" ? await svc.listSummaries(companyId, filters) @@ -321,7 +322,7 @@ export function executionWorkspaceRoutes(db: Db, opts: { pluginWorkerManager?: P and( eq(projectWorkspaces.id, existing.projectWorkspaceId), eq(projectWorkspaces.companyId, existing.companyId), - eq(projectWorkspaces.projectId, existing.projectId), + existing.projectId ? eq(projectWorkspaces.projectId, existing.projectId) : sql`false`, ), ) .then((rows) => rows[0] ?? null) diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index bce1dd02742..13c5e848f6a 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -3717,6 +3717,7 @@ export function issueRoutes( input: Record, ) { return ( + input.workspaceSelection !== undefined || input.parentId !== undefined || input.inheritExecutionWorkspaceFromIssueId !== undefined || input.projectWorkspaceId !== undefined || @@ -4890,31 +4891,11 @@ export function issueRoutes( executionWorkspacePreference?: string | null; executionWorkspaceSettings?: unknown; }) { - // Match create()'s project precedence before checking any assignment or - // trust policy. A workspace or explicit inheritance source can supply the - // project even when a caller omits projectId and the parent is projectless. if (input.projectId) return input.projectId; const sourceId = input.inheritExecutionWorkspaceFromIssueId ?? input.parentId; const source = sourceId ? await svc.getById(sourceId) : null; if (sourceId && (!source || source.companyId !== input.companyId)) throw notFound("Workspace inheritance issue not found"); - if (source?.projectId) return source.projectId; - const projectWorkspaceId = input.projectWorkspaceId ?? source?.projectWorkspaceId; - if (projectWorkspaceId) { - const [workspace] = await db.select({ projectId: projectWorkspaces.projectId }).from(projectWorkspaces) - .where(and(eq(projectWorkspaces.id, projectWorkspaceId), eq(projectWorkspaces.companyId, input.companyId))); - if (!workspace) throw notFound("Project workspace not found"); - return workspace.projectId; - } - const hasExecutionOverride = input.executionWorkspaceId !== undefined || - input.executionWorkspacePreference !== undefined || input.executionWorkspaceSettings !== undefined; - const executionWorkspaceId = input.executionWorkspaceId ?? (hasExecutionOverride ? null : source?.executionWorkspaceId); - if (executionWorkspaceId && (await instanceSettings.getExperimental()).enableIsolatedWorkspaces) { - const [workspace] = await db.select({ projectId: executionWorkspaces.projectId }).from(executionWorkspaces) - .where(and(eq(executionWorkspaces.id, executionWorkspaceId), eq(executionWorkspaces.companyId, input.companyId))); - if (!workspace) throw notFound("Execution workspace not found"); - return workspace.projectId; - } - return null; + return source?.projectId ?? null; } async function assertCanAssignTasks( @@ -7748,7 +7729,6 @@ export function issueRoutes( // must not install the guard, or it can clear the flag that // the other request still owns. // null - this function sent an error response, so the caller stops. - // The reopen is scoped to the issue company and project inside the service, and // it runs only after the route already authorized the request on the issue. async function reopenClosedIssueExecutionWorkspaceOrRespond( req: Request, @@ -7770,6 +7750,7 @@ export function issueRoutes( projectId: issue.projectId ?? null, }, actor: { agentId: actor.agentId, actorType: actor.actorType }, + authorizationActor: req.actor, }, ); if (result.ok) { @@ -12400,6 +12381,7 @@ export function issueRoutes( null; const createInput = { ...createBody, + workspaceSelectionActor: req.actor, projectId: createAssignmentScope.projectId, ...(taskBridgeOriginForActor(req) ?? {}), id: issueId, @@ -12764,6 +12746,7 @@ export function issueRoutes( ); const { issue, parentBlockerAdded } = await svc.createChild(parent.id, { ...createBody, + workspaceSelectionActor: req.actor, projectId: childAssignmentScope.projectId, ...(taskBridgeOriginForActor(req) ?? {}), id: issueId, @@ -14352,6 +14335,7 @@ export function issueRoutes( const postCommitIssueActions: IssuePostCommitAction[] = []; const issueUpdateData = { ...updateFields, + workspaceSelectionActor: req.actor, expectedExecutionPolicy, actorAgentId: actor.agentId ?? null, actorRunId: actor.agentId ? actor.runId : null, diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 9b113c62805..6efc1b198cd 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -76,6 +76,8 @@ import { upsertIssueFeedbackVoteSchema, upsertIssueWatchdogSchema, runnerGoalActionRequestSchema, + selectTaskWorkspaceSchema, + prepareWorkspaceRepositorySchema, // Project createProjectSchema, updateProjectSchema, @@ -4322,6 +4324,35 @@ registry.registerPath({ }, }); +registry.registerPath({ + method: "get", + path: "/api/issues/{id}/workspace", + tags: ["issues"], + summary: "Inspect the task workspace and repository preparation state", + request: { params: z.object({ id: z.string().uuid() }) }, + responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound }, +}); + +registry.registerPath({ + method: "put", + path: "/api/issues/{id}/workspace", + tags: ["issues"], + summary: "Select a task workspace for the next normal admission", + description: "Records workspace intent without restarting active work or changing the task's organizational project. Agents may select only for their bound task.", + request: { params: z.object({ id: z.string().uuid() }), body: jsonBody(selectTaskWorkspaceSchema) }, + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 409: r.conflict }, +}); + +registry.registerPath({ + method: "post", + path: "/api/issues/{id}/workspace/repositories", + tags: ["issues"], + summary: "Request repository preparation in the task workspace", + description: "Queues an authorized repository for the next normal admission. The response does not claim the repository is available in an already-running provider session.", + request: { params: z.object({ id: z.string().uuid() }), body: jsonBody(prepareWorkspaceRepositorySchema) }, + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 409: r.conflict }, +}); + registry.registerPath({ method: "get", path: "/api/issues/{id}/watchdog", diff --git a/server/src/routes/task-workspaces.ts b/server/src/routes/task-workspaces.ts new file mode 100644 index 00000000000..2fc458bf4bd --- /dev/null +++ b/server/src/routes/task-workspaces.ts @@ -0,0 +1,51 @@ +import { and, eq } from "drizzle-orm"; +import { issues, type Db } from "@paperclipai/db"; +import { Router, type Request } from "express"; +import { prepareWorkspaceRepositorySchema, selectTaskWorkspaceSchema } from "@paperclipai/shared"; +import { accessService } from "../services/access.js"; +import { executionWorkspaceService } from "../services/execution-workspaces.js"; +import { canActorReadExecutionWorkspace, issueReadSqlCondition } from "../services/authorization.js"; +import { projectToolContext } from "../services/project-tool-context.js"; +import { logActivity } from "../services/activity-log.js"; +import { forbidden, notFound } from "../errors.js"; +import { assertCompanyAccess, getActorInfo, hasCompanyAccess } from "./authz.js"; +import { validate } from "../middleware/validate.js"; + +export function taskWorkspaceRoutes(db: Db) { + const router = Router(); + const workspaces = executionWorkspaceService(db); + async function task(req: Request, write = false) { + const [issue] = await db.select().from(issues).where(and(eq(issues.id, req.params.id as string), await issueReadSqlCondition(db, req.actor))); + if (!issue) throw notFound("Task not found"); + if (!hasCompanyAccess(req, issue.companyId)) throw notFound("Task not found"); + assertCompanyAccess(req, issue.companyId); + const decision = await accessService(db).decide({ actor: req.actor, action: write ? "issue:mutate" : "issue:read", resource: { + type: "issue", companyId: issue.companyId, issueId: issue.id, + status: issue.status, assigneeAgentId: issue.assigneeAgentId, assigneeUserId: issue.assigneeUserId, + } }); + if (!decision.allowed) throw forbidden("Task workspace is outside this actor's authorization boundary"); + if (issue.executionWorkspaceId && !(await canActorReadExecutionWorkspace(db, req.actor, issue.executionWorkspaceId))) throw notFound("Execution workspace not found"); + if (write && req.actor.type === "agent") { + const context = await projectToolContext(db, req.actor, true, "Workspace"); + if (context.issue.id !== issue.id) throw forbidden("Workspace selection is scoped to the current task"); + } + return issue; + } + router.get("/issues/:id/workspace", async (req, res) => { + const issue = await task(req); + const view = await workspaces.inspectTaskWorkspace(issue.companyId, issue.id, req.actor); + res.json({ ...view, repositories: issue.executionWorkspaceId ? await workspaces.listTaskRepositories(issue.companyId, issue.executionWorkspaceId) : [], capabilities: { prepareRepository: "next_normal_admission", selectWorkspace: "next_normal_admission" }, fileLocations: { taskFiles: "workspace", personalInstructionsAndMemory: "AGENT_HOME", providerHome: "runtime_managed" } }); + }); + router.put("/issues/:id/workspace", validate(selectTaskWorkspaceSchema), async (req, res) => { + const issue = await task(req, true); + const result = await workspaces.selectTaskWorkspace({ companyId: issue.companyId, issueId: issue.id, actor: req.actor, ...req.body }); + const actor = getActorInfo(req); + await logActivity(db, { companyId: issue.companyId, ...actor, action: "issue.workspace.selection_requested", entityType: "issue", entityId: issue.id, details: { selection: req.body.selection, applies: "next_normal_admission" } }); + res.json(result); + }); + router.post("/issues/:id/workspace/repositories", validate(prepareWorkspaceRepositorySchema), async (req, res) => { + const issue = await task(req, true); + res.json(await workspaces.requestTaskRepository({ companyId: issue.companyId, issueId: issue.id, actor: req.actor, request: req.body })); + }); + return router; +} diff --git a/server/src/services/agent-directory-working-copies.ts b/server/src/services/agent-directory-working-copies.ts index 8bbead8531f..6c54dc381a4 100644 --- a/server/src/services/agent-directory-working-copies.ts +++ b/server/src/services/agent-directory-working-copies.ts @@ -71,7 +71,7 @@ export function agentDirectoryWorkingCopyService(db: Db, get: (companyId: string // while plugin capability discovery is cold after controller restart. runtimeRootDir: sandboxTransferRoot(row, target.remoteCwd), syncWorkspace: true, workspaceInboundMode: recovering ? "adopt_remote" : undefined, - workspaceBaseline: baseline(row), workspaceGitSnapshot: null, workspaceFileMode: "all", + workspaceBaseline: baseline(row), workspaceGitSnapshot: null, workspaceRepositories: [], workspaceFileMode: "all", workspaceExclude: [".paperclip-runtime", ".paperclip-runtime/**"] }); } async function observe(row: Copy, target?: AdapterExecutionTarget | null) { diff --git a/server/src/services/agent-file-checkpoints.ts b/server/src/services/agent-file-checkpoints.ts index a7bc07d68f6..980576ee198 100644 --- a/server/src/services/agent-file-checkpoints.ts +++ b/server/src/services/agent-file-checkpoints.ts @@ -107,7 +107,7 @@ export async function captureAgentFileCheckpoint(input: { stats = JSON.parse(result.stdout.trim()) as AgentFileCheckpointStats; await fs.rm(cacheDir, { recursive: true, force: true }); restore = await prepareAdapterExecutionTargetRuntime({ target, runId: input.runId, adapterKey: `agent-checkpoint-download-${path.basename(directory)}`, workspaceLocalDir: directory, - workspaceRemoteDir: remoteDirectory!, syncWorkspace: true, workspaceInboundMode: "adopt_remote", workspaceBaseline: { exclude: [], entries: new Map() }, workspaceGitSnapshot: null, workspaceFileMode: "all" }); + workspaceRemoteDir: remoteDirectory!, syncWorkspace: true, workspaceInboundMode: "adopt_remote", workspaceBaseline: { exclude: [], entries: new Map() }, workspaceGitSnapshot: null, workspaceRepositories: [], workspaceFileMode: "all" }); await restore.restoreWorkspace(); } return { directory, manifest: await validateAgentFileCheckpoint(directory, input.previous), stats, cleanup }; diff --git a/server/src/services/authorization.ts b/server/src/services/authorization.ts index 533cf186f5d..7bc119b86a4 100644 --- a/server/src/services/authorization.ts +++ b/server/src/services/authorization.ts @@ -92,6 +92,7 @@ export type AuthorizationAction = | "issue:mutate" | "issue:read" | "project:read" + | "project:write_workspace" | "runtime:manage" | "secrets:read" | "secrets:propose"; @@ -169,6 +170,7 @@ function companyIdForResource(resource: AuthorizationResource) { } function permissionForAction(action: AuthorizationAction): PermissionKey | null { + if (action === "project:write_workspace") return "tasks:assign"; if (action === "agent_config:read" || action === "agent_config:update" || action === "agent_instructions:update" || action === "skill_config:update") { return null; } @@ -1276,7 +1278,7 @@ export function authorizationService(db: Db | DbTransaction) { return lowTrustDeny("Issue is outside this low-trust boundary."); } - if (input.action === "tasks:assign") { + if (input.action === "tasks:assign" || input.action === "project:write_workspace") { if (input.resource.type !== "issue") { return lowTrustDeny("Low-trust task assignment is missing an issue resource."); } @@ -1382,6 +1384,7 @@ export function authorizationService(db: Db | DbTransaction) { input.action === "agent:read" || input.action === "agent:wake" || input.action === "project:read" || + input.action === "project:write_workspace" || input.action === "runtime:manage" || input.action === "secrets:read" || input.action === "secrets:propose" @@ -1452,6 +1455,7 @@ export function authorizationService(db: Db | DbTransaction) { input.action === "agent:read" || input.action === "agent:wake" || input.action === "project:read" || + input.action === "project:write_workspace" || input.action === "runtime:manage" || input.action === "secrets:read" || input.action === "secrets:propose" || @@ -1633,6 +1637,13 @@ export function authorizationService(db: Db | DbTransaction) { }): Promise { const permissionKey = permissionForAction(input.action); const companyId = companyIdForResource(input.resource); + if (input.action === "project:write_workspace" && (input.resource.type !== "issue" || !input.resource.projectId)) { + return deny({ action: input.action, reason: "deny_unsupported_action", explanation: "Workspace writes require a source project and task context." }); + } + const taskAssignmentAction = input.action === "tasks:assign" || input.action === "project:write_workspace"; + const requestedAssignmentPolicy = () => input.action === "project:write_workspace" && input.resource.type === "issue" + ? loadProjectAuthorizationPolicy(companyId, input.resource.projectId!).then(policy => evaluateAuthorizationPolicyForAssignment(policy, "Target project")) + : assignmentPolicyEffect(input.resource); /** * Shared default-open decision for issue write-influence channels. @@ -1920,7 +1931,7 @@ export function authorizationService(db: Db | DbTransaction) { explanation: "Board user id is required.", }); } - if (input.action === "tasks:assign") { + if (taskAssignmentAction) { if (!(await assignmentTargetIsInCompany(input.resource))) { return deny({ action: input.action, @@ -1928,7 +1939,7 @@ export function authorizationService(db: Db | DbTransaction) { explanation: "Task assignment target agent is not active in the target company.", }); } - const policyEffect = await assignmentPolicyEffect(input.resource); + const policyEffect = await requestedAssignmentPolicy(); taskAssignmentPolicyEffect = policyEffect; const policyDeny = await denyForAssignmentPolicyIfNeeded(policyEffect); if (policyDeny) return policyDeny; @@ -2040,10 +2051,10 @@ export function authorizationService(db: Db | DbTransaction) { explanation: `No board permission mapping exists for ${input.action}.`, }); } - if (input.action === "tasks:assign") { + if (taskAssignmentAction) { const grantDecision = await decideWithTaskAssignmentGrants("user", input.actor.userId); if (grantDecision.allowed) return grantDecision; - const policyEffect = taskAssignmentPolicyEffect ?? await assignmentPolicyEffect(input.resource); + const policyEffect = taskAssignmentPolicyEffect ?? await requestedAssignmentPolicy(); if (policyEffect.kind === "restricted") return denyRestrictedAssignmentPolicy(policyEffect); return grantDecision; } @@ -2353,7 +2364,7 @@ export function authorizationService(db: Db | DbTransaction) { }); } - if (input.action === "tasks:assign") { + if (taskAssignmentAction) { if (!isSimpleAssignableAgentStatus(actorAgent.status)) { return deny({ action: input.action, @@ -2368,7 +2379,7 @@ export function authorizationService(db: Db | DbTransaction) { explanation: "Task assignment target agent is not active in the target company.", }); } - const policyEffect = await assignmentPolicyEffect(input.resource); + const policyEffect = await requestedAssignmentPolicy(); const policyDeny = await denyForAssignmentPolicyIfNeeded(policyEffect); if (policyDeny) return policyDeny; if (policyEffect.kind === "restricted") { diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index 6a302b30fdc..2bd74bbb9d6 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -102,6 +102,7 @@ import { instanceSettingsService } from "./instance-settings.js"; import { registerSlackTaskAuthority, slackRunOrigin } from "./connectors/slack-authority.js"; import { buildChatCommunicationGuidance } from "./chat-communication-guidance.js"; +import { assertChatExecutionDefaultsAccess, assertChatTaskCreationAccess, resolveChatExecutionDefaults } from "./chat-execution-defaults.js"; function githubPolicyRecord(value: unknown): Record { return value && typeof value === "object" && !Array.isArray(value) ? value as Record : {}; } import { githubReviewCheckService } from "./chat-github-checks.js"; @@ -2166,6 +2167,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { botLabel: endpoint.botDisplayName ?? row.assignedAgentName, botAvatarUrl: endpoint.botAvatarUrl, communicationInstructions: endpoint.communicationInstructions, + executionDefaults: endpoint.executionDefaults, ...(endpoint.provider === "imessage-photon" && endpoint.botExternalId ? { photonAllocation: endpoint.botExternalId.startsWith("photon-project:") ? "shared" as const : "dedicated" as const } : {}), allowDirectMessages: endpoint.allowDirectMessages, requireAtMention: endpoint.requireAtMention, @@ -2393,7 +2395,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) { const initial = await endpointRecord(endpointId); if (!initial) throw notFound("Chat endpoint not found"); - if (initial.endpoint.provider === "agentmail") throw badRequest("Use the email inbox API for AgentMail"); + if (initial.endpoint.provider === "agentmail" && Object.keys(input).some((key) => key !== "executionDefaults")) throw badRequest("Use the email inbox API for AgentMail"); await withCredentialMutationLease( initial.endpoint, async (credentialLease) => { @@ -2454,6 +2456,15 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { values.allowUnlinkedPeople = input.allowUnlinkedPeople; await db.transaction(async (tx) => { await credentialLease.assertOwned(tx); + if (input.executionDefaults !== undefined) { + await assertChatExecutionDefaultsAccess(db, { + companyId: existing.endpoint.companyId, + assigneeAgentId: values.assignedAgentId ?? existing.endpoint.assignedAgentId, + actor: { type: "board", userId: actorUserId, source: actorUserId ? "session" : "local_implicit" }, + defaults: input.executionDefaults, + }, tx); + values.executionDefaults = input.executionDefaults; + } await tx .update(chatEndpoints) .set(values) @@ -8215,27 +8226,45 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) => { let conversation = existingConversation; if (!conversation) { + const [taskResource] = await taskTx.select({ executionDefaults: chatEndpointResources.executionDefaults }) + .from(chatEndpointResources).where(and(eq(chatEndpointResources.id, resource.id), eq(chatEndpointResources.endpointId, taskEndpoint.id))); + const executionDefaults = resolveChatExecutionDefaults(taskEndpoint.executionDefaults, taskResource?.executionDefaults); + const responsibleUserId = taskUserId ?? taskEndpoint.sponsorUserId; + await assertChatTaskCreationAccess(db, { + companyId: taskEndpoint.companyId, + assigneeAgentId: taskEndpoint.assignedAgentId, + actor: responsibleUserId ? { type: "board", userId: responsibleUserId, source: "session", ignoreInstanceAdmin: true } : { type: "none" }, + defaults: executionDefaults, + }, taskTx); + await assertChatTaskCreationAccess(db, { + companyId: taskEndpoint.companyId, + assigneeAgentId: taskEndpoint.assignedAgentId, + actor: { type: "agent", agentId: taskEndpoint.assignedAgentId, companyId: taskEndpoint.companyId, onBehalfOfUserId: responsibleUserId, source: "agent_key" }, + defaults: executionDefaults, + }, taskTx); const sessionGeneration = isLinear ? (latestConversation?.sessionGeneration ?? 0) + 1 : 1; const issue = await issuesSvc.create( - endpoint.companyId, + taskEndpoint.companyId, { title: safeTitle( githubAutomatic ? `PR #${githubAutomatic.context.pullNumber}: ${githubAutomatic.context.title}` : githubIssue ? `GitHub issue #${githubIssue.context.issueNumber}: ${githubIssue.context.title}` : message.text, - `${PROVIDER_LABELS[endpoint.provider]} conversation`, + `${PROVIDER_LABELS[taskEndpoint.provider]} conversation`, ), - description: `Started from ${PROVIDER_LABELS[endpoint.provider]}: ${resource.label}`, + description: `Started from ${PROVIDER_LABELS[taskEndpoint.provider]}: ${resource.label}`, status: "todo", priority: "medium", - assigneeAgentId: endpoint.assignedAgentId, - createdByUserId: taskUserId ?? endpoint.sponsorUserId, - responsibleUserId: taskUserId ?? endpoint.sponsorUserId, + assigneeAgentId: taskEndpoint.assignedAgentId, + createdByUserId: taskUserId ?? taskEndpoint.sponsorUserId, + responsibleUserId: taskUserId ?? taskEndpoint.sponsorUserId, originKind: "chat_channel", - originId: `${endpoint.id}:${thread.id}:${sessionGeneration}`, - idempotencyKey: `chat:${endpoint.id}:${thread.id}:${sessionGeneration}`, + originId: `${taskEndpoint.id}:${thread.id}:${sessionGeneration}`, + idempotencyKey: `chat:${taskEndpoint.id}:${thread.id}:${sessionGeneration}`, + ...(executionDefaults.projectId !== undefined ? { projectId: executionDefaults.projectId } : {}), + ...(executionDefaults.workspace ? { workspaceSelection: executionDefaults.workspace, workspaceSelectionSource: "channel" as const } : {}), }, taskTx, ); diff --git a/server/src/services/chat-channels/resource-reconciliation.ts b/server/src/services/chat-channels/resource-reconciliation.ts index ac0d1069d73..72ff07925b1 100644 --- a/server/src/services/chat-channels/resource-reconciliation.ts +++ b/server/src/services/chat-channels/resource-reconciliation.ts @@ -5,7 +5,8 @@ import { slackResourceLabelIsFallback } from "./slack/resources.js"; import { telegramResourceLabelIsFallback, prepareTelegramResourceMigration, migrateTelegramResourceConversations } from "./telegram/resources.js"; import { and, asc, eq, inArray, ne, sql } from "drizzle-orm"; import { chatConversations, chatDeliveries, chatEndpointResources, chatEndpoints, toolConnections, type Db } from "@paperclipai/db"; -import type { ChatEndpointSetupState } from "@paperclipai/shared"; +import { assertChatExecutionDefaultsAccess } from "../chat-execution-defaults.js"; +import type { ChatExecutionDefaults, ChatEndpointSetupState } from "@paperclipai/shared"; import { badRequest, conflict, notFound, unprocessable } from "../../errors.js"; import { redactSensitiveText } from "../../redaction.js"; import { notifyChatDeliveryWork, notifyChatEndpointWork } from "../chat-work-notifications.js"; @@ -163,7 +164,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc async function replaceResources( endpointId: string, - updates: Array<{ id: string; enabled: boolean }>, + updates: Array<{ id: string; enabled?: boolean; executionDefaults?: ChatExecutionDefaults | null }>, actorUserId?: string | null, options?: { initialGitHubImport?: boolean }, ) { @@ -173,7 +174,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc async function updateResourceSelection( endpointId: string, - updates: Array<{ id: string; enabled: boolean }>, + updates: Array<{ id: string; enabled?: boolean; executionDefaults?: ChatExecutionDefaults | null }>, actorUserId?: string | null, options?: { initialGitHubImport?: boolean; allGitHubRepositoriesEnabled?: boolean }, ) { @@ -194,6 +195,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc const [endpoint] = await tx .select({ companyId: chatEndpoints.companyId, + assignedAgentId: chatEndpoints.assignedAgentId, connectionId: chatEndpoints.connectionId, provider: chatEndpoints.provider, setup: chatEndpoints.setup, @@ -213,6 +215,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc id: chatEndpointResources.id, availability: chatEndpointResources.availability, enabled: chatEndpointResources.enabled, + executionDefaults: chatEndpointResources.executionDefaults, }) .from(chatEndpointResources) .where( @@ -233,7 +236,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc .for("no key update"); if (!bulk && rows.length !== new Set(ids).size) throw unprocessable("Every resource must belong to this endpoint"); - const selectedUpdates = bulk + const selectedUpdates: typeof updates = bulk ? rows.map((row) => ({ id: row.id, enabled: options!.allGitHubRepositoriesEnabled! })) : updates; const availabilityById = new Map( @@ -250,15 +253,27 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc "A destination must still be available from the provider before it can be enabled", { code: "chat_resource_unavailable", resourceId: unavailable.id }, ); - const finalEnabled = new Map( - selectedUpdates.map((entry) => [entry.id, entry.enabled]), - ); + const finalEnabled = new Map(rows.map((row) => [row.id, row.enabled])); + for (const entry of selectedUpdates) { + if (entry.enabled !== undefined) finalEnabled.set(entry.id, entry.enabled); + } + const finalDefaults = new Map(rows.map((row) => [row.id, row.executionDefaults])); + for (const entry of selectedUpdates) { + if (entry.executionDefaults === undefined) continue; + await assertChatExecutionDefaultsAccess(db, { + companyId: endpoint.companyId, + assigneeAgentId: endpoint.assignedAgentId, + actor: { type: "board", userId: actorUserId, source: actorUserId ? "session" : "local_implicit" }, + defaults: entry.executionDefaults, + }, tx); + finalDefaults.set(entry.id, entry.executionDefaults); + } const changes = rows - .filter((row) => row.enabled !== finalEnabled.get(row.id)) + .filter((row) => row.enabled !== finalEnabled.get(row.id) || JSON.stringify(row.executionDefaults) !== JSON.stringify(finalDefaults.get(row.id))) .map((row) => ({ resourceId: row.id, - before: { enabled: row.enabled }, - after: { enabled: finalEnabled.get(row.id)! }, + before: { enabled: row.enabled, ...(JSON.stringify(row.executionDefaults) !== JSON.stringify(finalDefaults.get(row.id)) ? { executionDefaults: row.executionDefaults } : {}) }, + after: { enabled: finalEnabled.get(row.id)!, ...(JSON.stringify(row.executionDefaults) !== JSON.stringify(finalDefaults.get(row.id)) ? { executionDefaults: finalDefaults.get(row.id) } : {}) }, })); if (bulk) { // One atomic database update, independent of pagination/search and @@ -276,7 +291,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc } else for (const entry of selectedUpdates) await tx .update(chatEndpointResources) - .set({ enabled: entry.enabled, updatedAt: new Date() }) + .set({ enabled: entry.enabled, ...(entry.executionDefaults !== undefined ? { executionDefaults: entry.executionDefaults } : {}), updatedAt: new Date() }) .where( and( eq(chatEndpointResources.companyId, endpoint.companyId), @@ -419,6 +434,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc (currentEndpoint.provider === "slack" && effect.resourceType === "channel" && effect.availability === "available"), + ...(previousResource ? { executionDefaults: previousResource.executionDefaults } : {}), metadata: effect.metadata ?? {}, }) .onConflictDoUpdate({ @@ -438,6 +454,7 @@ export function createChatResourceReconciliation(db: Db, options: ResourceReconc enabled: sql`${chatEndpointResources.enabled} or ${migratedEnabled}`, } : {}), + ...(previousResource ? { executionDefaults: previousResource.executionDefaults } : {}), metadata: effect.metadata ?? {}, updatedAt: now, }, diff --git a/server/src/services/chat-execution-defaults.ts b/server/src/services/chat-execution-defaults.ts new file mode 100644 index 00000000000..d0ff01df0a2 --- /dev/null +++ b/server/src/services/chat-execution-defaults.ts @@ -0,0 +1,69 @@ +import { chatExecutionDefaultsSchema, type ChatExecutionDefaults } from "@paperclipai/shared"; +import { type Db, projects } from "@paperclipai/db"; +import { and, eq } from "drizzle-orm"; +import { projectReadSqlCondition, type AuthorizationActor } from "./authorization.js"; +import { executionWorkspaceService } from "./execution-workspaces.js"; +import { forbidden, notFound } from "../errors.js"; +import { accessService } from "./access.js"; + +type DbTransaction = Parameters[0]>[0]; +type DefaultsAccessInput = { + companyId: string; + actor: AuthorizationActor; + assigneeAgentId?: string | null; + defaults: ChatExecutionDefaults | null | undefined; +}; + +export async function assertChatExecutionDefaultsAccess( + db: Db, + input: DefaultsAccessInput, + reader: Db | DbTransaction = db, +): Promise { + const defaults = chatExecutionDefaultsSchema.parse(input.defaults ?? {}); + if (defaults.projectId) { + const [project] = await reader.select({ id: projects.id }).from(projects).where(and( + eq(projects.id, defaults.projectId), eq(projects.companyId, input.companyId), + await projectReadSqlCondition(reader, input.actor), + )); + if (!project) throw notFound("Default project is unavailable or inaccessible"); + } + if (defaults.workspace) await executionWorkspaceService(db).validateSelection({ + companyId: input.companyId, actor: input.actor, selection: defaults.workspace, assigneeAgentId: input.assigneeAgentId, + }, reader); +} + +export async function assertChatTaskCreationAccess( + db: Db, + input: DefaultsAccessInput & { assigneeAgentId: string; parentIssueId?: string | null }, + transaction: Db | DbTransaction, +): Promise { + await assertChatExecutionDefaultsAccess(db, input, transaction); + if (!input.defaults?.projectId) return; + const scope = { + projectId: input.defaults.projectId, + parentIssueId: input.parentIssueId ?? null, + assigneeAgentId: input.assigneeAgentId, + assigneeUserId: null, + }; + const decision = await accessService(transaction as Db).decide({ + actor: input.actor, + action: "tasks:assign", + resource: { type: "issue", companyId: input.companyId, ...scope }, + scope, + }); + if (!decision.allowed) throw forbidden(decision.explanation); +} + +export function resolveChatExecutionDefaults( + endpoint: ChatExecutionDefaults | null | undefined, + resource: ChatExecutionDefaults | null | undefined, +): ChatExecutionDefaults { + const base = chatExecutionDefaultsSchema.parse(endpoint ?? {}); + const override = chatExecutionDefaultsSchema.parse(resource ?? {}); + return { + ...(base.projectId !== undefined ? { projectId: base.projectId } : {}), + ...(base.workspace !== undefined ? { workspace: base.workspace } : {}), + ...(override.projectId !== undefined ? { projectId: override.projectId } : {}), + ...(override.workspace !== undefined ? { workspace: override.workspace } : {}), + }; +} diff --git a/server/src/services/company-deletion.ts b/server/src/services/company-deletion.ts index 4ab40f03e69..b668c7ad01e 100644 --- a/server/src/services/company-deletion.ts +++ b/server/src/services/company-deletion.ts @@ -14,6 +14,7 @@ import { issues, issueComments, projects, + executionWorkspaces, goals, heartbeatRuns, runIdentityContexts, @@ -99,6 +100,7 @@ export async function deleteCompany(db: Db, id: string) { await tx.delete(companyLogos).where(eq(companyLogos.companyId, id)); await tx.delete(assets).where(eq(assets.companyId, id)); await tx.delete(goals).where(eq(goals.companyId, id)); + await tx.delete(executionWorkspaces).where(eq(executionWorkspaces.companyId, id)); await tx.delete(projects).where(eq(projects.companyId, id)); await agentLifecycleCompanyDeletion.deleteCompanyData(tx, id); const rows = await tx diff --git a/server/src/services/email-channels.ts b/server/src/services/email-channels.ts index db8b2c471b8..688aa2d6cda 100644 --- a/server/src/services/email-channels.ts +++ b/server/src/services/email-channels.ts @@ -47,6 +47,7 @@ import { resolveExecutionWorkspaceEnvironmentId } from "./execution-workspace-po import { emailConnectionService } from "./email-connections.js"; import { secretService } from "./secrets.js"; import { authorizationService } from "./authorization.js"; +import { assertChatTaskCreationAccess, resolveChatExecutionDefaults } from "./chat-execution-defaults.js"; import { issueService } from "./issues.js"; import { logActivity } from "./activity-log.js"; import { instanceSettingsService } from "./instance-settings.js"; @@ -1317,22 +1318,42 @@ export function emailChannelService(db: Db, options: EmailChannelOptions) { } if (!conversation) { const placement = await inboundPlacement( - endpoint.companyId, - endpoint.assignedAgentId, + current.companyId, + current.assignedAgentId, ); + const defaults = resolveChatExecutionDefaults(current.executionDefaults, null); + if ("projectId" in placement && defaults.projectId !== undefined && defaults.projectId !== placement.projectId) { + throw forbidden("Default project conflicts with the assigned agent's trust boundary"); + } + await assertChatTaskCreationAccess(db, { + companyId: current.companyId, + assigneeAgentId: current.assignedAgentId, + actor: current.sponsorUserId ? { type: "board", userId: current.sponsorUserId, source: "session", ignoreInstanceAdmin: true } : { type: "none" }, + defaults: { ...defaults, ...("projectId" in placement ? { projectId: placement.projectId } : {}) }, + parentIssueId: "parentId" in placement ? placement.parentId : null, + }, tx); + await assertChatTaskCreationAccess(db, { + companyId: current.companyId, + assigneeAgentId: current.assignedAgentId, + actor: { type: "agent", agentId: current.assignedAgentId, companyId: current.companyId, onBehalfOfUserId: current.sponsorUserId, source: "agent_key" }, + defaults: { ...defaults, ...("projectId" in placement ? { projectId: placement.projectId } : {}) }, + parentIssueId: "parentId" in placement ? placement.parentId : null, + }, tx); const task = await issueService(db).create( - endpoint.companyId, + current.companyId, { + ...(defaults.projectId !== undefined ? { projectId: defaults.projectId } : {}), ...placement, + ...(defaults.workspace ? { workspaceSelection: defaults.workspace, workspaceSelectionSource: "channel" as const } : {}), title: message.subject.slice(0, 200), - description: `Email conversation for ${endpoint.botExternalId}`, + description: `Email conversation for ${current.botExternalId}`, status: "todo", priority: "medium", - assigneeAgentId: endpoint.assignedAgentId, - responsibleUserId: endpoint.sponsorUserId, + assigneeAgentId: current.assignedAgentId, + responsibleUserId: current.sponsorUserId, originKind: "chat_channel", - originId: `email:${endpoint.id}:${message.thread_id}`, - idempotencyKey: `email:${endpoint.id}:${message.thread_id}`, + originId: `email:${current.id}:${message.thread_id}`, + idempotencyKey: `email:${current.id}:${message.thread_id}`, }, tx, ); diff --git a/server/src/services/execution-workspace-policy.ts b/server/src/services/execution-workspace-policy.ts index 0efb521ffaa..9d315ed2671 100644 --- a/server/src/services/execution-workspace-policy.ts +++ b/server/src/services/execution-workspace-policy.ts @@ -393,6 +393,29 @@ export function resolveExecutionWorkspaceMode(input: { return "shared_workspace"; } +interface ParsedIssueAssigneeAdapterOverrides { + adapterConfig: Record | null; + useProjectWorkspace: boolean | null; +} + +export function parseIssueAssigneeAdapterOverrides( + raw: unknown, +): ParsedIssueAssigneeAdapterOverrides | null { + const parsed = parseObject(raw); + const parsedAdapterConfig = parseObject(parsed.adapterConfig); + const adapterConfig = + Object.keys(parsedAdapterConfig).length > 0 ? parsedAdapterConfig : null; + const useProjectWorkspace = + typeof parsed.useProjectWorkspace === "boolean" + ? parsed.useProjectWorkspace + : null; + if (!adapterConfig && useProjectWorkspace === null) return null; + return { + adapterConfig, + useProjectWorkspace, + }; +} + function parseSharedWorkspaceConcurrency(raw: unknown): SharedWorkspaceConcurrency | undefined { return raw === "auto" || raw === "serialize" || raw === "allow" ? raw : undefined; } @@ -408,6 +431,7 @@ export function resolveSharedWorkspaceConcurrency(input: { export function buildExecutionWorkspaceAdapterConfig(input: { agentConfig: Record; + adapterConfigOverrides?: Record | null; projectPolicy: ProjectExecutionWorkspacePolicy | null; issueSettings: IssueExecutionWorkspaceSettings | null; mode: ParsedExecutionWorkspaceMode; @@ -450,5 +474,21 @@ export function buildExecutionWorkspaceAdapterConfig(input: { } } - return nextConfig; + return { ...nextConfig, ...input.adapterConfigOverrides }; +} + +export function taskWorkspaceRuntimeSelectionEnabled(input: { + legacyUiEnabled: boolean; + hasTypedSelection: boolean; + hasBinding: boolean; +}): boolean { + return input.legacyUiEnabled || input.hasTypedSelection || input.hasBinding; +} + +export function canApplyTaskWorkspaceSelectionAtAdmission(input: { + admittedInput: unknown; + restarting: boolean; + hasLeaseOwner: boolean; +}): boolean { + return input.admittedInput == null && !input.restarting && !input.hasLeaseOwner; } diff --git a/server/src/services/execution-workspace-repositories.ts b/server/src/services/execution-workspace-repositories.ts new file mode 100644 index 00000000000..c91685ea9a0 --- /dev/null +++ b/server/src/services/execution-workspace-repositories.ts @@ -0,0 +1,922 @@ +import { createHash, randomUUID } from "node:crypto"; +import { execFile as execFileCallback } from "node:child_process"; +import { isDeepStrictEqual, promisify } from "node:util"; +import fs from "node:fs/promises"; +import { constants as fsConstants } from "node:fs"; +import path from "node:path"; +import { and, eq, isNotNull, or, sql } from "drizzle-orm"; +import { activityLog, agents, heartbeatRuns, projects, projectWorkspaces, executionWorkspaceRepositoryAccess, executionWorkspaceRepositoryRoots, executionWorkspaceRepositories, executionWorkspaces, issues, type Db } from "@paperclipai/db"; +import { prepareWorkspaceRepositorySchema, type PrepareWorkspaceRepository, type ProjectRepository } from "@paperclipai/shared"; +import { withDirectoryPublicationLock } from "@paperclipai/adapter-utils/workspace-restore-merge"; +import { conflict, forbidden, notFound } from "../errors.js"; +import { parseNativeExecutionInput } from "../vendor/paperclip-runner/index.js"; +import { assertTaskWorkspaceAccess, assertTaskWorkspaceSourceProjectAccess } from "./task-workspace-source-access.js"; +import { authorizationService, canActorReadExecutionWorkspace, executionWorkspaceReadSqlCondition, issueReadSqlCondition, projectReadSqlCondition, type AuthorizationActor } from "./authorization.js"; +import { LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH, resolveCoreTrustPreset } from "./trust-preset-resolver.js"; +import { taskWorkspaceSelectableCondition } from "./task-workspace-selection.js"; +import { captureRunIdentity } from "./run-identity.js"; +import { toolAccessService } from "./tool-access.js"; +import { normalizeProjectRepositoryUrl } from "./project-repositories.js"; +import { buildGitAuthInvocation, createGitRemoteAuthProvider } from "./git-credentials.js"; +import { materializeManagedProjectWorkspace, ensureManagedRepositoriesIgnored } from "./managed-repository-checkout.js"; + +import { canonicalRepositoryRoot, repositoryRootsOverlap, legacyRepositoryRootMayMatch, configuredWorkspaceRoot, verifiedIsolatedWorktree, unusedIsolatedCopyTarget, isolatedWorkspaceSourceRoots, type WorkspaceSourceCopyKind } from "./execution-workspace-source-roots.js"; +export { canonicalRepositoryRoot } from "./execution-workspace-source-roots.js"; + +const execFile = promisify(execFileCallback); +type RepositoryRow = typeof executionWorkspaceRepositories.$inferSelect; +type DbTransaction = Parameters[0]>[0]; + +const controllerAcquisitionMessage = "New controller repository acquisition requires a trusted task. Existing verified public checkouts remain available."; +const lowTrustRepositoryMessage = "Low-trust repository reuse permits anonymous public URLs only; new acquisition is unavailable. Use an authorized configured source for private repositories."; + +function resolveAcquisitionSource(request: PrepareWorkspaceRepository["repository"], available: ProjectRepository[], anonymousOnly: boolean) { + if (anonymousOnly && request.kind === "catalog") throw forbidden(lowTrustRepositoryMessage); + return resolveTaskRepository(request, anonymousOnly ? [] : available); +} + +function assertAcquisitionReceipt(row: Pick, anonymousOnly: boolean) { + if (anonymousOnly && row.catalogRepositoryId) throw forbidden(lowTrustRepositoryMessage); +} + +export function resolveTaskRepository(request: PrepareWorkspaceRepository["repository"], available: ProjectRepository[]) { + const selected = request.kind === "catalog" ? available.find(repo => repo.id === request.id) : null; + if (request.kind === "catalog" && !selected) throw forbidden("Repository access is no longer available; refresh the repository catalog"); + const source = normalizeProjectRepositoryUrl(selected?.url ?? (request.kind === "url" ? request.url : "")); + const match = selected ?? available.find(repo => normalizeProjectRepositoryUrl(repo.url).url.toLowerCase() === source.url.toLowerCase()); + return { repoUrl: source.url, repositoryIdentity: source.url.toLowerCase(), catalogRepositoryId: match?.id ?? null }; +} + +export function taskRepositoryRelativePath(identity: string) { + return `.paperclip-repositories/task-repo-${createHash("sha256").update(identity).digest("hex").slice(0, 24)}`; +} + +export function assertRepositoryRetryMatches(row: Pick, identity: string, ref: string) { + if (row.repositoryIdentity !== identity || row.requestedRef !== ref) { + throw conflict("Repository preparation conflicts with an existing receipt. A different ref requires a separate workspace; existing files are never switched automatically."); + } +} + +async function resolveRepositoryRootSources(tx: Db | DbTransaction, input: { + companyId: string; cwd: string; sourceWorkspaceId?: string; inspectCurrentAliases?: boolean; preparingCopyProjectIds?: Set; +}) { + const canonicalCwd = await canonicalRepositoryRoot(input.cwd); + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const companyEntries = await tx.select().from(executionWorkspaceRepositoryAccess) + .where(eq(executionWorkspaceRepositoryAccess.companyId, input.companyId)); + const entries = companyEntries.filter(entry => entry.rootKey === rootKey); + const overlappingEntries = companyEntries.filter(entry => repositoryRootsOverlap(entry.canonicalCwd, canonicalCwd)); + if (entries.some(entry => entry.canonicalCwd !== canonicalCwd) + || overlappingEntries.some(entry => createHash("sha256").update(entry.canonicalCwd).digest("hex") !== entry.rootKey)) { + throw forbidden("Repository root identity conflicts with its retained access record"); + } + const proofs = entries.filter(entry => entry.kind === "isolated_copy"); + const copiedProjectIds = new Set(input.preparingCopyProjectIds ?? []); + if (proofs.length && await verifiedIsolatedWorktree(canonicalCwd)) { + for (const proof of proofs) copiedProjectIds.add(proof.scopeId); + } + const isCopiedAncestor = (projectId: string, sourceRoot: string) => copiedProjectIds.has(projectId) + && sourceRoot !== canonicalCwd && repositoryRootsOverlap(sourceRoot, canonicalCwd) + && !path.relative(sourceRoot, canonicalCwd).startsWith(`..${path.sep}`) + && path.relative(sourceRoot, canonicalCwd) !== ".."; + const sharedSourceRoots = new Map>(); + const addSharedSource = (id: string, cwd: string) => { + const roots = sharedSourceRoots.get(id) ?? new Set(); + roots.add(cwd); sharedSourceRoots.set(id, roots); + }; + for (const entry of overlappingEntries) if (entry.kind === "source") addSharedSource(entry.scopeId, entry.canonicalCwd); + const sharedSourceIds = new Set(sharedSourceRoots.keys()); + const isolatedSourceIds = new Set(overlappingEntries.filter(entry => entry.kind === "isolated_source").map(entry => entry.scopeId)); + const sourceIds = new Set([...sharedSourceIds, ...isolatedSourceIds]); + if (input.sourceWorkspaceId) { sourceIds.add(input.sourceWorkspaceId); sharedSourceIds.add(input.sourceWorkspaceId); addSharedSource(input.sourceWorkspaceId, canonicalCwd); } + const workspaces = await tx.select({ id: executionWorkspaces.id, cwd: executionWorkspaces.cwd }).from(executionWorkspaces) + .where(eq(executionWorkspaces.companyId, input.companyId)); + for (const workspace of workspaces) { + if (!workspace.cwd) continue; + const peerRoot = await canonicalRepositoryRoot(workspace.cwd).catch((error: NodeJS.ErrnoException) => { + if (["ENOTDIR", "EACCES", "EPERM", "ELOOP", "ENAMETOOLONG"].includes(error.code ?? "")) return null; + throw error; + }); + if (peerRoot && repositoryRootsOverlap(peerRoot, canonicalCwd)) { + sourceIds.add(workspace.id); sharedSourceIds.add(workspace.id); addSharedSource(workspace.id, peerRoot); + } + } + const inventory = await tx.select({ id: executionWorkspaceRepositories.id, workspaceId: executionWorkspaces.id, cwd: executionWorkspaces.cwd, + identity: executionWorkspaceRepositories.repositoryIdentity }).from(executionWorkspaceRepositories) + .innerJoin(executionWorkspaces, eq(executionWorkspaces.id, executionWorkspaceRepositories.executionWorkspaceId)) + .where(and(eq(executionWorkspaceRepositories.companyId, input.companyId), eq(executionWorkspaces.companyId, input.companyId))); + const namespace = path.join(canonicalCwd, ".paperclip-repositories"); + const namespaceEntry = await fs.lstat(namespace).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + const publishedNames = new Set(); + if (namespaceEntry) { + if (!namespaceEntry.isDirectory() || namespaceEntry.isSymbolicLink() || await fs.realpath(namespace) !== namespace) { + throw forbidden("Repository namespace has no verifiable contained ownership"); + } + for (const name of await fs.readdir(namespace)) if (name.startsWith("task-repo-")) publishedNames.add(name); + } + const repositoryOrigins = await tx.select({ id: executionWorkspaceRepositoryAccess.scopeId }).from(executionWorkspaceRepositoryAccess) + .where(and(eq(executionWorkspaceRepositoryAccess.companyId, input.companyId), eq(executionWorkspaceRepositoryAccess.kind, "repository"))); + const knownOrigins = new Set(repositoryOrigins.map(origin => origin.id)); + const recognizedNames = new Set(); + for (const peer of inventory) { + const name = path.basename(taskRepositoryRelativePath(peer.identity)); + const published = publishedNames.has(name); + const peerRoot = peer.cwd ? await fs.realpath(peer.cwd).catch((error: NodeJS.ErrnoException) => { + if (["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP", "ENAMETOOLONG"].includes(error.code ?? "")) return null; + throw error; + }) : null; + const matches = sourceIds.has(peer.workspaceId) || (peerRoot !== null && repositoryRootsOverlap(peerRoot, canonicalCwd)) + || (published && peerRoot === null && !knownOrigins.has(peer.id)) + || (peerRoot === null && !entries.some(entry => entry.kind === "reader") && peer.cwd && await legacyRepositoryRootMayMatch(peer.cwd, canonicalCwd)); + if (matches) { + sourceIds.add(peer.workspaceId); + if (!isolatedSourceIds.has(peer.workspaceId) || (peerRoot && repositoryRootsOverlap(peerRoot, canonicalCwd))) { + sharedSourceIds.add(peer.workspaceId); addSharedSource(peer.workspaceId, peerRoot ?? canonicalCwd); + } + if (published) recognizedNames.add(name); + } + } + if ([...publishedNames].some(name => !recognizedNames.has(name))) throw forbidden("Published repository has no retained workspace provenance"); + const configured = await tx.select({ projectId: projectWorkspaces.projectId, cwd: projectWorkspaces.cwd, repoUrl: projectWorkspaces.repoUrl }) + .from(projectWorkspaces).where(eq(projectWorkspaces.companyId, input.companyId)); + const sourceProjectIds = new Set(overlappingEntries.filter(entry => (entry.kind === "project_source" || entry.kind === "isolated_project_source")).map(entry => entry.scopeId)); + const sharedProjectIds = new Set(overlappingEntries.filter(entry => entry.kind === "shared_project_source" && !isCopiedAncestor(entry.scopeId, entry.canonicalCwd)).map(entry => entry.scopeId)); + const isolatedProjectIds = new Set(overlappingEntries.filter(entry => entry.kind === "isolated_project_source").map(entry => entry.scopeId)); + if (sourceIds.size) { + const owners = await tx.select({ id: executionWorkspaces.id, projectId: executionWorkspaces.projectId, strategyType: executionWorkspaces.strategyType }).from(executionWorkspaces).where(and( + eq(executionWorkspaces.companyId, input.companyId), or(...[...sourceIds].map(id => eq(executionWorkspaces.id, id))))); + for (const owner of owners) if (owner.projectId) { + sourceProjectIds.add(owner.projectId); + if (owner.strategyType === "git_worktree" || !sharedSourceIds.has(owner.id) + || [...sharedSourceRoots.get(owner.id) ?? []].every(cwd => isCopiedAncestor(owner.projectId!, cwd))) isolatedProjectIds.add(owner.projectId); + else sharedProjectIds.add(owner.projectId); + } + } + for (const source of configured) { + if (sharedProjectIds.has(source.projectId)) continue; + const sourceRoot = await configuredWorkspaceRoot(source, true); + if (!sourceRoot || !repositoryRootsOverlap(sourceRoot, canonicalCwd)) continue; + sourceProjectIds.add(source.projectId); + if (!isCopiedAncestor(source.projectId, sourceRoot)) sharedProjectIds.add(source.projectId); + } + for (const entry of overlappingEntries) if (entry.kind === "project_source" && !isolatedProjectIds.has(entry.scopeId)) { + sharedProjectIds.add(entry.scopeId); + } + return { canonicalCwd, rootKey, entries, overlappingEntries, sourceIds, sharedSourceIds, isolatedSourceIds, sourceProjectIds, sharedProjectIds, isolatedProjectIds }; +} + +async function assertRootProjectAccess(db: Db | DbTransaction, actor: AuthorizationActor, companyId: string, projectIds: Set, + write?: { sharedProjectIds: Set } & Omit[4]>, "write">) { + for (const projectId of projectIds) { + const [project] = await db.select({ id: projects.id }).from(projects).where(and( + eq(projects.id, projectId), eq(projects.companyId, companyId), await projectReadSqlCondition(db, actor))); + if (!project) throw forbidden("Workspace source project is no longer accessible"); + await assertTaskWorkspaceSourceProjectAccess(db, actor, companyId, projectId, { + ...write, write: write?.sharedProjectIds.has(projectId), + }); + } +} + +async function retainedRootReader(tx: DbTransaction, companyId: string, reader: Pick) { + const [run] = await tx.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, reader.scopeId), eq(heartbeatRuns.companyId, companyId))); + const [agent] = reader.agentId ? await tx.select({ id: agents.id }).from(agents).where(and(eq(agents.id, reader.agentId), eq(agents.companyId, companyId))) : []; + const [issue] = reader.issueId ? await tx.select().from(issues).where(and(eq(issues.id, reader.issueId), eq(issues.companyId, companyId))) : []; + if (!run || !agent || !issue || run.agentId !== reader.agentId || run.responsibleUserId !== reader.responsibleUserId + || (run.nativeIssueId ?? run.contextSnapshot?.issueId ?? run.contextSnapshot?.taskId ?? run.issueId) !== reader.issueId) { + throw forbidden("Repository root retains a reader whose identity is no longer available"); + } + const actor: AuthorizationActor = { type: "agent", source: "agent_jwt", companyId, + agentId: reader.agentId!, runId: reader.scopeId, onBehalfOfUserId: reader.responsibleUserId === "local-board" ? null : reader.responsibleUserId }; + return { actor, issue }; +} + +export async function lockExecutionWorkspaceRootProvenance(tx: DbTransaction, companyId: string) { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`task-repository-roots:${companyId}`}, 0))`); +} + +export async function preserveConfiguredProjectWorkspaceRootProvenance(tx: DbTransaction, input: { + companyId: string; projectId: string; cwd: string | null; repoUrl?: string | null; +}): Promise { + const canonicalCwd = await configuredWorkspaceRoot(input, false); + if (!canonicalCwd) return; + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const entries = await tx.select().from(executionWorkspaceRepositoryAccess).where(and( + eq(executionWorkspaceRepositoryAccess.companyId, input.companyId), eq(executionWorkspaceRepositoryAccess.rootKey, rootKey))); + if (entries.some(entry => entry.canonicalCwd !== canonicalCwd)) throw forbidden("Repository root identity conflicts with its retained access record"); + for (const kind of ["project_source", "shared_project_source"] as const) { + if (!entries.some(entry => entry.kind === kind && entry.scopeId === input.projectId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, canonicalCwd, rootKey, kind, scopeId: input.projectId }); + } + } +} + +export async function preserveExecutionWorkspaceRootProvenance(tx: DbTransaction, input: { + companyId: string; workspaceId: string; cwd: string | null; +}): Promise { + if (!input.cwd) return; + const directory = await fs.stat(input.cwd).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (!directory) return; + if (!directory.isDirectory()) throw forbidden("Workspace files cannot be detached from unverifiable provenance"); + const { canonicalCwd, rootKey, entries, sharedSourceIds, isolatedSourceIds, sourceProjectIds, sharedProjectIds, isolatedProjectIds } = await resolveRepositoryRootSources(tx, { + companyId: input.companyId, cwd: input.cwd, sourceWorkspaceId: input.workspaceId, inspectCurrentAliases: true, + }); + for (const [kind, ids] of [["source", sharedSourceIds], ["isolated_source", isolatedSourceIds], ["project_source", sourceProjectIds], ["shared_project_source", sharedProjectIds], ["isolated_project_source", isolatedProjectIds]] as const) { + for (const scopeId of ids) { + if (!entries.some(entry => entry.kind === kind && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, canonicalCwd, rootKey, kind, scopeId }); + } + } + } +} + +export async function assertExecutionWorkspaceRootReadAccess(db: Db | DbTransaction, input: { + companyId: string; cwd: string; actor: AuthorizationActor; sourceWorkspaceId?: string; +}): Promise { + const { sourceIds, sourceProjectIds } = await resolveRepositoryRootSources(db, { ...input, inspectCurrentAliases: true }); + for (const sourceId of sourceIds) await assertTaskWorkspaceAccess(db, input.actor, input.companyId, sourceId); + await assertRootProjectAccess(db, input.actor, input.companyId, sourceProjectIds); +} + +export type IsolatedWorkspaceSourceAccess = { + companyId: string; + roots: string[]; + workspaceIds: string[]; + projectIds: string[]; +}; + +async function retainGitMetadataOwners(tx: DbTransaction, input: { + companyId: string; commonRoot: string; workspaceIds: Set; projectIds: Set; +}): Promise { + const closure = await resolveRepositoryRootSources(tx, { companyId: input.companyId, cwd: input.commonRoot, inspectCurrentAliases: true }); + const workspaceIds = new Set([...closure.sourceIds, ...input.workspaceIds]); + const projectIds = new Set([...closure.sourceProjectIds, ...input.projectIds]); + for (const reader of closure.overlappingEntries.filter(entry => entry.kind === "reader")) { + const { actor } = await retainedRootReader(tx, input.companyId, reader); + for (const id of workspaceIds) await assertTaskWorkspaceAccess(tx, actor, input.companyId, id); + await assertRootProjectAccess(tx, actor, input.companyId, projectIds); + } + for (const [kind, ids] of [["source", workspaceIds], ["project_source", projectIds]] as const) { + for (const scopeId of ids) if (!closure.entries.some(entry => entry.kind === kind && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, + canonicalCwd: closure.canonicalCwd, rootKey: closure.rootKey, kind, scopeId }); + } + } +} + +export async function assertIsolatedWorkspaceSourceReadAccess(db: Db, input: { + companyId: string; sourceCwd: string; actor: AuthorizationActor; copyKind?: WorkspaceSourceCopyKind; +}): Promise { + const { roots, commonRoot } = await isolatedWorkspaceSourceRoots(db, input.companyId, input.sourceCwd, input.copyKind); + return db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + const workspaceIds = new Set(), projectIds = new Set(); + for (const cwd of roots) { + const closure = await resolveRepositoryRootSources(tx, { companyId: input.companyId, cwd, inspectCurrentAliases: true }); + for (const id of closure.sourceIds) { + await assertTaskWorkspaceAccess(tx, input.actor, input.companyId, id); + workspaceIds.add(id); + } + await assertRootProjectAccess(tx, input.actor, input.companyId, closure.sourceProjectIds); + for (const id of closure.sourceProjectIds) projectIds.add(id); + } + await retainGitMetadataOwners(tx, { companyId: input.companyId, commonRoot, workspaceIds, projectIds }); + return { companyId: input.companyId, roots, workspaceIds: [...workspaceIds], projectIds: [...projectIds] }; + }); +} + +export async function retainIsolatedWorkspaceSourceAccess(db: Db, input: { + companyId: string; sourceCwd: string; targetCwd: string; actor: AuthorizationActor; + captured: IsolatedWorkspaceSourceAccess; issueId?: string; phase?: "preparing" | "verified"; copyKind?: WorkspaceSourceCopyKind; +}): Promise { + if (input.captured.companyId !== input.companyId) throw forbidden("Isolated copy source identity changed"); + let sourceFailure: unknown; + const roots = new Set(input.captured.roots); + try { for (const cwd of (await isolatedWorkspaceSourceRoots(db, input.companyId, input.sourceCwd, input.copyKind)).roots) roots.add(cwd); } + catch (error) { sourceFailure = error; } + const canonicalCwd = await canonicalRepositoryRoot(input.targetCwd); + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const denied = await db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + const existing = await tx.select().from(executionWorkspaceRepositoryAccess).where(and( + eq(executionWorkspaceRepositoryAccess.companyId, input.companyId), eq(executionWorkspaceRepositoryAccess.rootKey, rootKey))); + if (existing.some(entry => entry.canonicalCwd !== canonicalCwd)) throw forbidden("Repository root identity conflicts with its retained access record"); + const workspaceIds = new Set(input.captured.workspaceIds), projectIds = new Set(input.captured.projectIds); + let failure = sourceFailure; + for (const cwd of roots) { + try { + const closure = await resolveRepositoryRootSources(tx, { companyId: input.companyId, cwd, inspectCurrentAliases: true }); + for (const id of closure.sourceIds) workspaceIds.add(id); + for (const id of closure.sourceProjectIds) projectIds.add(id); + } catch (error) { failure ??= error; } + } + for (const id of workspaceIds) { + try { await assertTaskWorkspaceAccess(tx, input.actor, input.companyId, id); } + catch (error) { failure ??= error; } + } + for (const id of projectIds) { + try { await assertRootProjectAccess(tx, input.actor, input.companyId, new Set([id])); } + catch (error) { failure ??= error; } + } + for (const [kind, ids] of [["isolated_source", workspaceIds], ["isolated_project_source", projectIds]] as const) { + for (const scopeId of ids) if (!existing.some(entry => entry.kind === kind && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, rootKey, canonicalCwd, kind, scopeId }); + } + } + const phase = input.phase ?? "preparing"; + let preparingCopyProjectIds: Set | undefined; + let verifiedCopyProjectIds: Set | undefined; + try { + const targetEntry = await fs.lstat(input.targetCwd).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (targetEntry?.isSymbolicLink()) throw forbidden("Isolated copy target cannot be a symbolic link"); + const linkedWorktree = input.copyKind !== "repository"; + const verifiedTarget = linkedWorktree && await verifiedIsolatedWorktree(canonicalCwd, roots); + if (linkedWorktree && (phase === "verified" || verifiedTarget)) { + if (!verifiedTarget) throw forbidden("Isolated copy target is not a verified Git worktree"); + preparingCopyProjectIds = projectIds; + verifiedCopyProjectIds = projectIds; + } else if (linkedWorktree && await unusedIsolatedCopyTarget(input.targetCwd)) { + preparingCopyProjectIds = projectIds; + } + } catch (error) { failure ??= error; } + try { + const target = await resolveRepositoryRootSources(tx, { companyId: input.companyId, cwd: canonicalCwd, preparingCopyProjectIds }); + await assertRootProjectTrust(tx, { companyId: input.companyId, actor: input.actor, issueId: input.issueId, projectIds: target.sourceProjectIds }); + for (const id of target.sourceIds) await assertTaskWorkspaceAccess(tx, input.actor, input.companyId, id); + await assertRootProjectAccess(tx, input.actor, input.companyId, target.sourceProjectIds, + { sharedProjectIds: target.sharedProjectIds, issueId: input.issueId }); + for (const reader of target.overlappingEntries.filter(entry => entry.kind === "reader")) { + const { actor, issue } = await retainedRootReader(tx, input.companyId, reader); + await assertRootProjectTrust(tx, { companyId: input.companyId, actor, issueId: issue.id, projectIds: target.sourceProjectIds }); + for (const id of target.sourceIds) await assertTaskWorkspaceAccess(tx, actor, input.companyId, id); + await assertRootProjectAccess(tx, actor, input.companyId, target.sourceProjectIds); + } + } catch (error) { failure ??= error; } + if (!failure && verifiedCopyProjectIds) { + for (const scopeId of verifiedCopyProjectIds) if (!existing.some(entry => entry.kind === "isolated_copy" && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, rootKey, canonicalCwd, kind: "isolated_copy", scopeId }); + } + } + return failure; + }); + if (denied) throw denied; +} + +export type TaskWorkspaceCreationTrustContext = { + parentIssueId?: string | null; projectId?: string | null; assigneeAgentId?: string | null; executionPolicy?: unknown; +}; + +async function assertRootProjectTrust(db: Db | DbTransaction, input: { + companyId: string; projectIds: Set; actor: AuthorizationActor; + issueId?: string | null; sourceWorkspaceId?: string; assigneeAgentId?: string | null; taskCreation?: TaskWorkspaceCreationTrustContext; +}) { + if (!input.projectIds.size) return; + const owners = await db.select().from(projects).where(and(eq(projects.companyId, input.companyId), + or(...[...input.projectIds].map(id => eq(projects.id, id))))); + const restricted = owners.filter(owner => resolveCoreTrustPreset({ companyId: input.companyId, workspaceSourceProject: owner }).kind !== "standard"); + if (!restricted.length) return; + const incompatible = () => forbidden("Physical workspace source trust is incompatible with the task's execution policy"); + const runId = input.actor.runId; + const [run] = runId ? await db.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, runId), eq(heartbeatRuns.companyId, input.companyId))) : []; + const creation = !input.issueId && input.actor.type === "agent" && runId ? input.taskCreation : undefined; + const runIssueId = run?.nativeIssueId ?? run?.contextSnapshot?.issueId ?? run?.contextSnapshot?.taskId ?? run?.issueId; + const issueId = input.issueId ?? (creation && typeof runIssueId === "string" ? runIssueId : null); + const [issue] = issueId ? await db.select().from(issues).where(and(eq(issues.id, issueId), eq(issues.companyId, input.companyId))) : []; + const agentId = input.actor.type === "agent" ? input.actor.agentId : issue?.assigneeAgentId ?? input.assigneeAgentId; + const [agent] = agentId ? await db.select().from(agents).where(and(eq(agents.id, agentId), eq(agents.companyId, input.companyId))) : []; + const [project] = issue?.projectId ? await db.select().from(projects).where(and(eq(projects.id, issue.projectId), eq(projects.companyId, input.companyId))) : []; + const workspaceId = input.sourceWorkspaceId ?? issue?.executionWorkspaceId; + const [workspace] = workspaceId ? await db.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, workspaceId), eq(executionWorkspaces.companyId, input.companyId))) : []; + const [source] = workspace?.projectId ? await db.select().from(projects).where(and(eq(projects.id, workspace.projectId), eq(projects.companyId, input.companyId))) : []; + if ((agentId && !agent) || (runId && (!run || run.agentId !== agentId || !issue + || (run.nativeIssueId ?? run.contextSnapshot?.issueId ?? run.contextSnapshot?.taskId ?? run.issueId) !== issue.id))) throw incompatible(); + const retainedPolicy = run?.contextSnapshot?.executionPolicy; + const current = resolveCoreTrustPreset({ companyId: input.companyId, agent, project, workspaceSourceProject: source, issue, + run: run ? { companyId: input.companyId, executionPolicy: retainedPolicy } : null }); + const selected = run + ? resolveCoreTrustPreset({ companyId: input.companyId, run: { companyId: input.companyId, executionPolicy: retainedPolicy } }) + : current; + if (selected.kind !== "low_trust_review" || current.kind !== "low_trust_review" + || !isDeepStrictEqual(selected.boundary, current.boundary)) throw incompatible(); + if (creation) { + const sponsor = run!.responsibleUserId === "local-board" ? null : run!.responsibleUserId; + if ((input.actor.onBehalfOfUserId ?? null) !== sponsor) throw incompatible(); + let parentId = creation.parentIssueId; + const ancestors: Array = []; + for (let depth = 0; parentId && depth < LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH; depth++) { + const [parent] = await db.select().from(issues).where(and(eq(issues.id, parentId), eq(issues.companyId, input.companyId))); + if (!parent || ancestors.some(ancestor => ancestor.id === parent.id)) throw incompatible(); + ancestors.push(parent); + if (parent.id === issue!.id) break; + parentId = parent.parentId; + } + if (ancestors.at(-1)?.id !== issue!.id) throw incompatible(); + const decision = await authorizationService(db).decide({ actor: input.actor, action: "issue:mutate", + resource: { type: "issue", companyId: input.companyId, parentIssueId: creation.parentIssueId, + projectId: creation.projectId, assigneeAgentId: creation.assigneeAgentId } }); + if (!decision.allowed) throw incompatible(); + const [childAgent] = creation.assigneeAgentId ? await db.select().from(agents).where(and( + eq(agents.id, creation.assigneeAgentId), eq(agents.companyId, input.companyId))) : []; + if (creation.assigneeAgentId && !childAgent) throw incompatible(); + for (const candidate of [...ancestors, { companyId: input.companyId, projectId: creation.projectId, + executionPolicy: creation.executionPolicy }]) { + const [candidateProject] = candidate.projectId ? await db.select().from(projects).where(and( + eq(projects.id, candidate.projectId), eq(projects.companyId, input.companyId))) : []; + if (candidate.projectId && !candidateProject) throw incompatible(); + const prospective = resolveCoreTrustPreset({ companyId: input.companyId, + agent: "id" in candidate ? agent : childAgent, project: candidateProject, workspaceSourceProject: source, + issue: candidate, run: { companyId: input.companyId, executionPolicy: retainedPolicy } }); + if (prospective.kind !== "low_trust_review" || !isDeepStrictEqual(prospective.boundary, selected.boundary)) throw incompatible(); + } + } + const selectedPolicy = { trustPreset: selected.preset, authorizationPolicy: { + trustPreset: selected.preset, trustBoundary: selected.boundary, + } }; + for (const owner of restricted) { + const combined = resolveCoreTrustPreset({ companyId: input.companyId, workspaceSourceProject: owner, + run: { companyId: input.companyId, executionPolicy: selectedPolicy } }); + if (combined.kind !== "low_trust_review" || !isDeepStrictEqual(combined.boundary, selected.boundary)) throw incompatible(); + } +} + +export async function assertExecutionWorkspaceRootWriteAccess(db: Db | DbTransaction, input: { + companyId: string; cwd: string; actor: AuthorizationActor; sourceWorkspaceId?: string; taskCreation?: TaskWorkspaceCreationTrustContext; +} & Omit[4]>, "write">): Promise { + const { sourceIds, sourceProjectIds, sharedProjectIds } = await resolveRepositoryRootSources(db, { ...input, inspectCurrentAliases: true }); + for (const sourceId of sourceIds) await assertTaskWorkspaceAccess(db, input.actor, input.companyId, sourceId); + await assertRootProjectAccess(db, input.actor, input.companyId, sourceProjectIds, { ...input, sharedProjectIds }); + await assertRootProjectTrust(db, { ...input, projectIds: sourceProjectIds }); +} + +export function executionWorkspaceRepositoryService(db: Db) { + async function catalog(companyId: string, userId: string | null, localTrusted: boolean) { + return (await toolAccessService(db).listProjectRepositories(companyId, userId, localTrusted)).repositories; + } + async function requiresAnonymousAcquisition(reader: Db | DbTransaction, input: { + issue: typeof issues.$inferSelect; workspace: typeof executionWorkspaces.$inferSelect | null; + agentId?: string | null; runId?: string | null; + }) { + const { issue, workspace } = input; + const agentId = input.agentId ?? issue.assigneeAgentId; + const [agent] = agentId ? await reader.select().from(agents).where(and(eq(agents.id, agentId), eq(agents.companyId, issue.companyId))) : []; + if (agentId && !agent) throw forbidden("Task repository agent is unavailable"); + const [project] = issue.projectId ? await reader.select().from(projects).where(and(eq(projects.id, issue.projectId), eq(projects.companyId, issue.companyId))) : []; + const [workspaceSourceProject] = workspace?.projectId ? await reader.select().from(projects).where(and(eq(projects.id, workspace.projectId), eq(projects.companyId, issue.companyId))) : []; + const runId = input.runId ?? issue.executionRunId; + const [run] = runId ? await reader.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, runId), eq(heartbeatRuns.companyId, issue.companyId))) : []; + if (runId && (!run || (input.runId && run.agentId !== agentId) || (run.nativeIssueId ?? run.contextSnapshot?.issueId ?? run.contextSnapshot?.taskId ?? run.issueId) !== issue.id)) { + throw forbidden("Task repository run identity is unavailable"); + } + const trust = resolveCoreTrustPreset({ companyId: issue.companyId, agent, project, workspaceSourceProject, issue, + run: run ? { companyId: run.companyId, executionPolicy: run.contextSnapshot?.executionPolicy } : null }); + if (trust.kind === "denied") throw forbidden(trust.detail); + return trust.kind === "low_trust_review"; + } + async function assertCurrentControllerAcquisition(input: { companyId: string; issueId: string; workspaceId: string; agentId: string; runId: string }) { + const [issue] = await db.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId))); + const [workspace] = await db.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, input.companyId))); + if (!issue || !workspace || issue.executionWorkspaceId !== workspace.id) throw forbidden("Task repository workspace binding is unavailable"); + if (await requiresAnonymousAcquisition(db, { issue, workspace, agentId: input.agentId, runId: input.runId })) throw forbidden(controllerAcquisitionMessage); + } + async function reserveRootPolicy(tx: DbTransaction, companyId: string, cwd: string, mode: "anonymous_only" | "managed") { + const canonicalCwd = await canonicalRepositoryRoot(cwd); + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const reservations = await tx.select().from(executionWorkspaceRepositoryRoots) + .where(eq(executionWorkspaceRepositoryRoots.companyId, companyId)); + const existing = reservations.find(entry => entry.rootKey === rootKey); + const overlapping = reservations.filter(entry => repositoryRootsOverlap(entry.canonicalCwd, canonicalCwd)); + if ((existing && existing.canonicalCwd !== canonicalCwd) + || overlapping.some(entry => entry.mode !== mode || createHash("sha256").update(entry.canonicalCwd).digest("hex") !== entry.rootKey)) { + throw forbidden(lowTrustRepositoryMessage); + } + if (mode === "anonymous_only") { + const managed = await tx.select({ cwd: executionWorkspaces.cwd, repositoryIdentity: executionWorkspaceRepositories.repositoryIdentity }).from(executionWorkspaceRepositories) + .innerJoin(executionWorkspaces, eq(executionWorkspaces.id, executionWorkspaceRepositories.executionWorkspaceId)) + .where(and(eq(executionWorkspaceRepositories.companyId, companyId), eq(executionWorkspaces.companyId, companyId), + isNotNull(executionWorkspaceRepositories.catalogRepositoryId))); + if (managed.length) { + const namespace = path.join(canonicalCwd, ".paperclip-repositories"); + const namespaceEntry = await fs.lstat(namespace).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (namespaceEntry) { + if (!namespaceEntry.isDirectory() || namespaceEntry.isSymbolicLink() || await fs.realpath(namespace) !== namespace) { + throw forbidden(lowTrustRepositoryMessage); + } + for (const identity of new Set(managed.map(peer => peer.repositoryIdentity))) { + const entry = await fs.lstat(path.join(canonicalCwd, taskRepositoryRelativePath(identity))).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (entry) throw forbidden(lowTrustRepositoryMessage); + } + } + } + for (const peer of managed) { + if (peer.cwd && await legacyRepositoryRootMayMatch(peer.cwd, canonicalCwd)) throw forbidden(lowTrustRepositoryMessage); + } + } + if (!existing) await tx.insert(executionWorkspaceRepositoryRoots).values({ companyId, canonicalCwd, rootKey, mode }); + return canonicalCwd; + } + type RootReader = { runId: string; issueId: string; agentId: string }; + async function authorizeRootAccess(tx: DbTransaction, input: { + companyId: string; cwd: string; sourceWorkspaceId?: string; reader?: RootReader; actor?: AuthorizationActor; issueId?: string; preserveOwnAdmission?: boolean; + }) { + const { canonicalCwd, rootKey, entries, overlappingEntries, sourceIds, sharedSourceIds, isolatedSourceIds, sourceProjectIds, sharedProjectIds, isolatedProjectIds } = await resolveRepositoryRootSources(tx, input); + const readers = overlappingEntries.filter(entry => entry.kind === "reader"); + if (input.reader && !entries.some(entry => entry.kind === "reader" && entry.scopeId === input.reader!.runId)) { + const [run] = await tx.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, input.reader.runId), eq(heartbeatRuns.companyId, input.companyId))); + readers.push({ id: randomUUID(), companyId: input.companyId, rootKey, canonicalCwd, kind: "reader", scopeId: input.reader.runId, + issueId: input.reader.issueId, agentId: input.reader.agentId, responsibleUserId: run?.responsibleUserId ?? null, createdAt: new Date(), updatedAt: new Date() }); + } + const ownAdmissionOnly = input.preserveOwnAdmission && input.reader && input.sourceWorkspaceId + && sourceIds.size === 1 && sourceIds.has(input.sourceWorkspaceId) + && readers.every(reader => reader.issueId === input.reader!.issueId); + for (const reader of readers) { + const { actor, issue } = await retainedRootReader(tx, input.companyId, reader); + await assertRootProjectTrust(tx, { companyId: input.companyId, projectIds: sourceProjectIds, actor, issueId: issue.id, + sourceWorkspaceId: input.reader?.runId === reader.scopeId ? input.sourceWorkspaceId : undefined }); + await assertRootProjectAccess(tx, actor, input.companyId, sourceProjectIds, + input.reader?.runId === reader.scopeId ? { sharedProjectIds, issueId: issue.id } : undefined); + if (ownAdmissionOnly) { + const [ownSource] = await tx.select({ id: executionWorkspaces.id }).from(executionWorkspaces).where(and( + eq(executionWorkspaces.companyId, input.companyId), eq(executionWorkspaces.id, input.sourceWorkspaceId!), + await executionWorkspaceReadSqlCondition(tx, { ...actor, onBehalfOfUserId: null }), + actor.onBehalfOfUserId ? await executionWorkspaceReadSqlCondition(tx, { + type: "board", userId: actor.onBehalfOfUserId, ignoreInstanceAdmin: true, + }) : undefined)); + if (!ownSource) throw forbidden("Task workspace access is no longer available"); + } else for (const sourceId of sourceIds) await assertTaskWorkspaceAccess(tx, actor, input.companyId, sourceId); + const sourceWorkspaces = sourceIds.size ? await tx.select().from(executionWorkspaces).where(and( + eq(executionWorkspaces.companyId, input.companyId), or(...[...sourceIds].map(id => eq(executionWorkspaces.id, id))))) : []; + for (const sourceWorkspace of sourceWorkspaces.length ? sourceWorkspaces : [null]) { + if (await requiresAnonymousAcquisition(tx, { issue, workspace: sourceWorkspace, agentId: reader.agentId!, runId: reader.scopeId })) { + await reserveRootPolicy(tx, input.companyId, canonicalCwd, "anonymous_only"); + } + } + } + if (input.actor) { + if (!input.issueId) throw forbidden("Workspace write authorization requires a task"); + await assertRootProjectTrust(tx, { ...input, projectIds: sourceProjectIds, actor: input.actor }); + await assertRootProjectAccess(tx, input.actor, input.companyId, sourceProjectIds, { sharedProjectIds, issueId: input.issueId }); + for (const sourceId of sourceIds) await assertTaskWorkspaceAccess(tx, input.actor, input.companyId, sourceId); + } + for (const [kind, ids] of [["source", sharedSourceIds], ["isolated_source", isolatedSourceIds], ["project_source", sourceProjectIds], + ["shared_project_source", sharedProjectIds], ["isolated_project_source", isolatedProjectIds]] as const) { + for (const scopeId of ids) { + if (!entries.some(entry => entry.kind === kind && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, rootKey, canonicalCwd, kind, scopeId }); + } + } + } + for (const reader of readers) { + if (reader.rootKey === rootKey && !entries.some(entry => entry.kind === "reader" && entry.scopeId === reader.scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values(reader); + } + } + } + async function reserveRootAccess(input: Parameters[1]) { + return db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + await authorizeRootAccess(tx, input); + }); + } + async function reserveAdmissionRoot(companyId: string, cwd: string, mode: "anonymous_only" | "managed") { + return db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, companyId); + return reserveRootPolicy(tx, companyId, cwd, mode); + }); + } + async function request(input: { companyId: string; issueId: string; actor: AuthorizationActor; request: PrepareWorkspaceRepository }) { + const parsed = prepareWorkspaceRepositorySchema.parse(input.request); + const [requestedIssue] = await db.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId), await issueReadSqlCondition(db, input.actor))); + if (!requestedIssue) throw notFound("Task not found"); + const issueResource = (issue: typeof issues.$inferSelect) => ({ type: "issue" as const, companyId: issue.companyId, issueId: issue.id, + status: issue.status, assigneeAgentId: issue.assigneeAgentId, assigneeUserId: issue.assigneeUserId }); + const decision = await authorizationService(db).decide({ actor: input.actor, action: "issue:mutate", resource: issueResource(requestedIssue) }); + if (!decision.allowed) throw forbidden("Task workspace modification is not allowed"); + let userId = input.actor.userId ?? null; + let localTrusted = input.actor.source === "local_implicit"; + if (input.actor.type === "agent") { + if (input.actor.source !== "agent_jwt" || !input.actor.agentId || !input.actor.runId || input.actor.companyId !== input.companyId) throw forbidden("Repository preparation requires an authenticated task run"); + const identity = await captureRunIdentity(db, { companyId: input.companyId, agentId: input.actor.agentId, runId: input.actor.runId }); + const runIssueId = identity.run.nativeIssueId ?? identity.run.contextSnapshot?.issueId; + if (runIssueId !== input.issueId) throw forbidden("Repository preparation is scoped to the current task"); + userId = identity.run.responsibleUserId; + localTrusted = userId === "local-board"; + if (!userId) throw forbidden("Repository access requires a responsible user"); + } + const available = await catalog(input.companyId, userId, localTrusted); + const requestedRef = parsed.ref ?? "HEAD"; + const receipt = await db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + const [issue] = await tx.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId), await issueReadSqlCondition(tx, input.actor))).for("no key update"); + if (!issue) throw notFound("Task not found"); + const currentDecision = await authorizationService(tx).decide({ actor: input.actor, action: "issue:mutate", resource: issueResource(issue) }); + if (!currentDecision.allowed) throw forbidden("Task workspace modification is not allowed"); + if (input.actor.type === "agent" && !["standard", "skill_test"].includes(issue.workMode)) throw forbidden("Repository preparation is unavailable in Ask or Plan mode"); + if (!issue.executionWorkspaceId) throw conflict("The task has no admitted workspace yet. Start it once before preparing a repository."); + if (!(await canActorReadExecutionWorkspace(db, input.actor, issue.executionWorkspaceId))) throw notFound("Execution workspace not found"); + const [workspace] = await tx.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, issue.executionWorkspaceId), eq(executionWorkspaces.companyId, input.companyId), taskWorkspaceSelectableCondition())).for("no key update"); + if (!workspace) throw conflict("Execution workspace is unavailable"); + await assertTaskWorkspaceAccess(tx, input.actor, input.companyId, workspace.id, { write: true, issueId: input.issueId }); + const anonymousOnly = await requiresAnonymousAcquisition(tx, { issue, workspace, + agentId: input.actor.agentId, runId: input.actor.runId }); + const source = resolveAcquisitionSource(parsed.repository, available, anonymousOnly); + if (anonymousOnly || source.catalogRepositoryId) { + if (!workspace.cwd) throw conflict("The task workspace has no directory for repository preparation"); + await reserveRootPolicy(tx, input.companyId, workspace.cwd, anonymousOnly ? "anonymous_only" : "managed"); + } + if (!workspace.cwd) throw conflict("The task workspace has no directory for repository preparation"); + await authorizeRootAccess(tx, { companyId: input.companyId, cwd: workspace.cwd, sourceWorkspaceId: workspace.id, actor: input.actor, issueId: input.issueId }); + if (anonymousOnly) throw forbidden(controllerAcquisitionMessage); + const existing = await tx.select().from(executionWorkspaceRepositories).where(and(eq(executionWorkspaceRepositories.executionWorkspaceId, workspace.id), or(eq(executionWorkspaceRepositories.repositoryIdentity, source.repositoryIdentity), sql`${executionWorkspaceRepositories.requestKeys} ? ${parsed.requestKey}`))); + for (const row of existing) { + assertAcquisitionReceipt(row, anonymousOnly); + assertRepositoryRetryMatches(row, source.repositoryIdentity, requestedRef); + } + if (existing[0]) { + const row = existing[0]; + if (!row.requestKeys.includes(parsed.requestKey)) { + const [updated] = await tx.update(executionWorkspaceRepositories).set({ requestKeys: [...row.requestKeys, parsed.requestKey] }).where(eq(executionWorkspaceRepositories.id, row.id)).returning(); + return updated; + } + return row; + } + const [row] = await tx.insert(executionWorkspaceRepositories).values({ companyId: input.companyId, executionWorkspaceId: workspace.id, requestedByIssueId: input.issueId, ...source, relativePath: taskRepositoryRelativePath(source.repositoryIdentity), requestedRef, requestKey: parsed.requestKey, requestKeys: [parsed.requestKey] }).returning(); + const canonicalCwd = await canonicalRepositoryRoot(workspace.cwd); + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, + rootKey: createHash("sha256").update(canonicalCwd).digest("hex"), canonicalCwd, kind: "repository", scopeId: row.id }); + await tx.insert(activityLog).values({ companyId: input.companyId, actorType: input.actor.type === "agent" ? "agent" : "user", actorId: input.actor.agentId ?? input.actor.userId ?? "local-board", action: "workspace.repository.requested", entityType: "execution_workspace", entityId: workspace.id, runId: input.actor.runId ?? null, responsibleUserId: userId, details: { repositoryId: row.id, issueId: input.issueId, applies: "next_normal_admission" } }); + return row; + }); + return { kind: "requires_next_admission" as const, applies: "next_normal_admission" as const, operationId: receipt.id, repository: receipt, reason: "immutable_admitted_workspace" }; + } + async function list(companyId: string, workspaceId: string) { + return db.select().from(executionWorkspaceRepositories).where(and(eq(executionWorkspaceRepositories.companyId, companyId), eq(executionWorkspaceRepositories.executionWorkspaceId, workspaceId))); + } + type PublicationInput = { companyId: string; issueId: string; workspaceId: string; cwd: string; agentId: string; runId: string }; + async function publicationBinding(tx: DbTransaction, input: PublicationInput) { + const [issue] = await tx.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId))); + const [workspace] = await tx.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, input.companyId))); + if (!issue || !workspace || issue.executionWorkspaceId !== workspace.id || !workspace.cwd + || await canonicalRepositoryRoot(workspace.cwd) !== await canonicalRepositoryRoot(input.cwd)) { + throw forbidden("Task repository publication binding is unavailable"); + } + await requiresAnonymousAcquisition(tx, { issue, workspace, agentId: input.agentId, runId: input.runId }); + const [run] = await tx.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, input.runId), eq(heartbeatRuns.companyId, input.companyId))); + const { actor } = await retainedRootReader(tx, input.companyId, { + scopeId: input.runId, issueId: input.issueId, agentId: input.agentId, responsibleUserId: run?.responsibleUserId ?? null, + }); + return { workspace, actor }; + } + async function guardForPublication(input: PublicationInput) { + await db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + const { workspace } = await publicationBinding(tx, input); + await authorizeRootAccess(tx, { companyId: input.companyId, cwd: input.cwd, sourceWorkspaceId: workspace.id, reader: input }); + }); + } + async function prepareConfiguredRepositorySource(input: PublicationInput & { sourceCwd: string }): Promise<() => Promise> { + const roots = new Set(), workspaceIds = new Set(), projectIds = new Set(); + const retain = async () => { + let sourceFailure: unknown; + let commonRoot: string | undefined; + try { + roots.add(await fs.realpath(input.sourceCwd)); + const source = await isolatedWorkspaceSourceRoots(db, input.companyId, input.sourceCwd, "repository"); + commonRoot = source.commonRoot; + for (const cwd of source.roots) roots.add(cwd); + } catch (error) { sourceFailure = error; } + const denied = await db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, input.companyId); + const { workspace, actor } = await publicationBinding(tx, input); + const canonicalCwd = await canonicalRepositoryRoot(input.cwd); + const rootKey = createHash("sha256").update(canonicalCwd).digest("hex"); + const existing = await tx.select().from(executionWorkspaceRepositoryAccess).where(and( + eq(executionWorkspaceRepositoryAccess.companyId, input.companyId), eq(executionWorkspaceRepositoryAccess.rootKey, rootKey))); + if (existing.some(entry => entry.canonicalCwd !== canonicalCwd)) throw forbidden("Repository root identity conflicts with its retained access record"); + let failure = sourceFailure; + for (const cwd of roots) { + try { + const source = await resolveRepositoryRootSources(tx, { companyId: input.companyId, cwd }); + for (const id of source.sourceIds) workspaceIds.add(id); + for (const id of source.sourceProjectIds) projectIds.add(id); + } catch (error) { failure ??= error; } + } + for (const id of workspaceIds) { + try { await assertTaskWorkspaceAccess(tx, actor, input.companyId, id); } + catch (error) { failure ??= error; } + } + for (const id of projectIds) { + try { await assertRootProjectAccess(tx, actor, input.companyId, new Set([id])); } + catch (error) { failure ??= error; } + } + for (const [kind, ids] of [["isolated_source", workspaceIds], ["isolated_project_source", projectIds]] as const) { + for (const scopeId of ids) if (!existing.some(entry => entry.kind === kind && entry.scopeId === scopeId)) { + await tx.insert(executionWorkspaceRepositoryAccess).values({ companyId: input.companyId, rootKey, canonicalCwd, kind, scopeId }); + } + } + try { + await authorizeRootAccess(tx, { companyId: input.companyId, cwd: input.cwd, sourceWorkspaceId: workspace.id, + actor, issueId: input.issueId }); + } catch (error) { failure ??= error; } + if (!failure && commonRoot) { + try { await retainGitMetadataOwners(tx, { companyId: input.companyId, commonRoot, workspaceIds, projectIds }); } + catch (error) { failure ??= error; } + } + return failure; + }); + if (denied) throw denied; + }; + await retain(); + return retain; + } + async function reserveForRecovery(input: { companyId: string; issueId: string; workspaceId: string; cwd: string; agentId: string; runId: string }) { + const [issue] = await db.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId))); + const [workspace] = await db.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, input.companyId))); + if (!issue) throw forbidden("Task repository recovery identity is unavailable"); + if (!workspace) { + const [run] = await db.select().from(heartbeatRuns).where(and(eq(heartbeatRuns.id, input.runId), eq(heartbeatRuns.companyId, input.companyId))); + let execution; + try { execution = parseNativeExecutionInput(run?.runnerProfileJson?.nativeExecutionInput); } catch { } + const binding = execution?.binding; + if (input.workspaceId !== input.runId || run?.runtimeMode !== "native" || !binding + || binding.companyId !== input.companyId || binding.runId !== input.runId + || binding.agentId !== input.agentId || binding.issueId !== input.issueId + || binding.executionWorkspaceId !== input.runId) { + throw forbidden("Task repository recovery identity is unavailable"); + } + } + const anonymousOnly = await requiresAnonymousAcquisition(db, { issue, workspace: workspace ?? null, agentId: input.agentId, runId: input.runId }); + if (anonymousOnly) await reserveAdmissionRoot(input.companyId, input.cwd, "anonymous_only"); + else if ((await list(input.companyId, input.workspaceId)).some(row => row.catalogRepositoryId)) { + await reserveAdmissionRoot(input.companyId, input.cwd, "managed"); + } + await reserveRootAccess({ companyId: input.companyId, cwd: input.cwd, reader: input, + ...(workspace ? { sourceWorkspaceId: workspace.id } : {}), + preserveOwnAdmission: !(await list(input.companyId, input.workspaceId)).length }); + } + async function sharedPublicationPin(input: { issueId: string; row: RepositoryRow; root: string; actor: AuthorizationActor; anonymousOnly: boolean; ownership: { repositoryId?: string; pinnedCommit?: string } | null }) { + const candidates = await db.select({ repository: executionWorkspaceRepositories, workspace: executionWorkspaces }) + .from(executionWorkspaceRepositories).innerJoin(executionWorkspaces, eq(executionWorkspaces.id, executionWorkspaceRepositories.executionWorkspaceId)) + .where(and(eq(executionWorkspaceRepositories.companyId, input.row.companyId), eq(executionWorkspaces.companyId, input.row.companyId), + eq(executionWorkspaceRepositories.repositoryIdentity, input.row.repositoryIdentity), eq(executionWorkspaceRepositories.relativePath, input.row.relativePath))); + let pin = input.row.pinnedCommit; + for (const { repository, workspace } of candidates) { + if (repository.id === input.row.id || !workspace.cwd) continue; + const candidatePin = repository.pinnedCommit ?? (input.ownership?.repositoryId === repository.id ? input.ownership.pinnedCommit : null); + if (!candidatePin || !/^[a-f0-9]{40,64}$/.test(candidatePin)) continue; + if (await fs.realpath(workspace.cwd).catch(() => null) !== input.root) continue; + assertAcquisitionReceipt(repository, input.anonymousOnly); + assertRepositoryRetryMatches(repository, input.row.repositoryIdentity, input.row.requestedRef); + if (!(await canActorReadExecutionWorkspace(db, input.actor, workspace.id))) throw forbidden("The shared repository retains files from a workspace outside this actor's access"); + await assertTaskWorkspaceAccess(db, input.actor, input.row.companyId, workspace.id, { write: true, issueId: input.issueId }); + if (pin && pin !== candidatePin) throw conflict("Shared repository publication receipts disagree; repair is required before reuse"); + pin = candidatePin; + } + return pin; + } + async function prepareForAdmission(input: { companyId: string; issueId: string; workspaceId: string; cwd: string; agentId: string; runId: string; responsibleUserId: string | null }) { + const rows = await list(input.companyId, input.workspaceId); + const actor: AuthorizationActor = { type: "agent", source: "agent_jwt", companyId: input.companyId, + agentId: input.agentId, runId: input.runId, + onBehalfOfUserId: input.responsibleUserId === "local-board" ? null : input.responsibleUserId }; + const [issue] = await db.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId))); + const [workspace] = await db.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, input.companyId))); + if (!issue || !workspace || issue.executionWorkspaceId !== workspace.id) throw forbidden("Task repository workspace binding is unavailable"); + const anonymousOnly = await requiresAnonymousAcquisition(db, { issue, workspace: workspace ?? null, agentId: input.agentId, runId: input.runId }); + if (!rows.length) { + if (anonymousOnly) await reserveAdmissionRoot(input.companyId, input.cwd, "anonymous_only"); + await reserveRootAccess({ companyId: input.companyId, cwd: input.cwd, reader: input, + sourceWorkspaceId: workspace.id, preserveOwnAdmission: true }); + return []; + } + const acquisitionRestricted = anonymousOnly; + if (acquisitionRestricted && rows.some(row => row.state !== "ready" || !row.pinnedCommit || !/^[a-f0-9]{40,64}$/.test(row.pinnedCommit))) { + throw forbidden(controllerAcquisitionMessage); + } + await assertTaskWorkspaceAccess(db, actor, input.companyId, input.workspaceId, { write: true, issueId: input.issueId }); + const root = await fs.realpath(input.cwd); + const available = acquisitionRestricted ? [] : await catalog(input.companyId, input.responsibleUserId, input.responsibleUserId === "local-board"); + const resolveAuth = createGitRemoteAuthProvider(db, input.companyId, { issueId: input.issueId, heartbeatRunId: input.runId, agentId: input.agentId, responsibleUserId: input.responsibleUserId }); + const sources = rows.map(row => { + assertAcquisitionReceipt(row, anonymousOnly); + return resolveAcquisitionSource(row.catalogRepositoryId ? { kind: "catalog", id: row.catalogRepositoryId } : { kind: "url", url: row.repoUrl }, available, anonymousOnly); + }); + if (anonymousOnly || sources.some(source => source.catalogRepositoryId)) { + const reservedRoot = await reserveAdmissionRoot(input.companyId, root, anonymousOnly ? "anonymous_only" : "managed"); + if (reservedRoot !== root) throw conflict("Repository workspace root changed during admission"); + } + await reserveRootAccess({ companyId: input.companyId, cwd: root, sourceWorkspaceId: input.workspaceId, reader: input }); + const repositoryRoot = path.join(root, ".paperclip-repositories"); + if (!acquisitionRestricted) await fs.mkdir(repositoryRoot, { recursive: true }); + if ((await fs.lstat(repositoryRoot)).isSymbolicLink() || await fs.realpath(repositoryRoot) !== repositoryRoot) throw conflict("Repository directory escapes the task workspace"); + let stagingParent = root; + if (!acquisitionRestricted) { + for (const name of [".paperclip-runtime", "repository-staging"]) { + stagingParent = path.join(stagingParent, name); + await fs.mkdir(stagingParent, { mode: 0o700 }).catch((error: NodeJS.ErrnoException) => { + if (error.code !== "EEXIST") throw error; + }); + const entry = await fs.lstat(stagingParent); + if (!entry.isDirectory() || entry.isSymbolicLink() || await fs.realpath(stagingParent) !== stagingParent) { + throw conflict("Repository staging directory escapes the task workspace"); + } + } + const ignorePath = path.join(stagingParent, ".gitignore"); + const ignoreEntry = await fs.lstat(ignorePath).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (ignoreEntry && (!ignoreEntry.isFile() || ignoreEntry.isSymbolicLink())) { + throw conflict("Repository staging ignore file is not a regular file"); + } + const ignoreTemporary = path.join(stagingParent, `.ignore-${randomUUID()}.tmp`); + try { + await fs.writeFile(ignoreTemporary, "*\n", { flag: "wx", mode: 0o600 }); + await fs.rename(ignoreTemporary, ignorePath); + } finally { await fs.rm(ignoreTemporary, { force: true }); } + await ensureManagedRepositoriesIgnored(root); + } + const prepared: Array<{ id: string; cwd: string; repoUrl: string; relativePath: string; pinnedCommit: string; branchName: string | null }> = []; + for (const [index, row] of rows.entries()) { + const source = sources[index]; + if (source.repositoryIdentity !== row.repositoryIdentity || row.relativePath !== taskRepositoryRelativePath(row.repositoryIdentity)) throw conflict("Repository preparation receipt does not match its source"); + const cwd = path.join(root, row.relativePath); + await withDirectoryPublicationLock(cwd, async () => { + const existing = await fs.lstat(cwd).catch(() => null); + if (existing && (!existing.isDirectory() || existing.isSymbolicLink() || await fs.realpath(cwd) !== cwd)) throw conflict("Repository checkout path is not a contained directory"); + if (existing) { + const gitDir = path.join(cwd, ".git"); + const metadata = await fs.lstat(gitDir).catch(() => null); + if (!metadata?.isDirectory() || metadata.isSymbolicLink() || await fs.realpath(gitDir) !== gitDir) throw conflict("Repository Git metadata escapes the managed checkout"); + } + if (acquisitionRestricted && !existing) throw forbidden(controllerAcquisitionMessage); + const anonymous = buildGitAuthInvocation({ token: "", source: "managed_connection", secretName: null }); + anonymous.env = { ...anonymous.env, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" }; + if (!acquisitionRestricted) await db.update(executionWorkspaceRepositories).set({ state: "preparing", catalogRepositoryId: source.catalogRepositoryId, failureCode: null, updatedAt: new Date() }).where(eq(executionWorkspaceRepositories.id, row.id)); + try { + const ownershipPath = path.join(cwd, ".git", "paperclip-workspace-owner.json"); + const readOwnership = async () => { + const handle = await fs.open(ownershipPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK).catch(() => null); + if (!handle) return null; + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.size > 4096) return null; + const bytes = Buffer.alloc(4097); + const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0); + return bytesRead <= 4096 ? JSON.parse(bytes.subarray(0, bytesRead).toString("utf8")) : null; + } catch { return null; } finally { await handle.close(); } + }; + const initialOwnership = await readOwnership(); + const authorizedPin = await sharedPublicationPin({ issueId: input.issueId, row, root, actor, anonymousOnly, ownership: initialOwnership }); + if (existing && !authorizedPin && initialOwnership?.repositoryId !== row.id) { + throw conflict("Repository directory has no matching preparation receipt; repair it before retrying"); + } + if (!acquisitionRestricted) { + await assertCurrentControllerAcquisition(input); + const result = await materializeManagedProjectWorkspace(cwd, { + repoUrl: source.repoUrl, + stagingParent, + repoRef: authorizedPin ?? (row.requestedRef === "HEAD" ? null : row.requestedRef), + resolveGitAuth: source.catalogRepositoryId ? resolveAuth : async () => anonymous, + beforePublish: async cloneCwd => { + await assertCurrentControllerAcquisition(input); + await reserveRootAccess({ companyId: input.companyId, cwd: root, sourceWorkspaceId: input.workspaceId, reader: input }); + const pinnedCommit = (await execFile("git", ["-C", cloneCwd, "rev-parse", "HEAD"], { timeout: 30_000 })).stdout.trim(); + await fs.writeFile(path.join(cloneCwd, ".git", "paperclip-workspace-owner.json"), JSON.stringify({ version: 1, repositoryId: row.id, pinnedCommit }), { flag: "wx", mode: 0o600 }); + }, + }); + if (result.warning) throw conflict(result.warning); + } + const git = async (...args: string[]) => (await execFile("git", ["-C", cwd, ...args], { timeout: 30_000, env: { ...process.env, GIT_NO_LAZY_FETCH: "1" } })).stdout.trim(); + const origin = normalizeProjectRepositoryUrl(await git("remote", "get-url", "origin")); + if (origin.url.toLowerCase() !== row.repositoryIdentity) throw conflict("Repository checkout belongs to a different origin"); + const ownership = await readOwnership(); + if (!authorizedPin && (ownership?.repositoryId !== row.id || !/^[a-f0-9]{40,64}$/.test(ownership.pinnedCommit))) throw conflict("Repository publication has no valid preparation receipt"); + const pinnedCommit: string = authorizedPin ?? ownership.pinnedCommit; + if (acquisitionRestricted) await git("cat-file", "-e", `${pinnedCommit}^{commit}`); + const branchName = await git("symbolic-ref", "--quiet", "--short", "HEAD").catch(() => null); + await db.update(executionWorkspaceRepositories).set({ state: "ready", pinnedCommit, branchName, failureCode: null, updatedAt: new Date() }).where(eq(executionWorkspaceRepositories.id, row.id)); + prepared.push({ id: row.id, cwd, repoUrl: row.repoUrl, relativePath: row.relativePath, pinnedCommit, branchName }); + } catch (error) { + await db.update(executionWorkspaceRepositories).set({ state: "needs_repair", failureCode: "repository_preparation_failed", updatedAt: new Date() }).where(eq(executionWorkspaceRepositories.id, row.id)); + throw error; + } + }); + } + return prepared; + } + return { request, list, prepareForAdmission, reserveForRecovery, guardForPublication, prepareConfiguredRepositorySource }; +} diff --git a/server/src/services/execution-workspace-source-roots.ts b/server/src/services/execution-workspace-source-roots.ts new file mode 100644 index 00000000000..36830eac7cf --- /dev/null +++ b/server/src/services/execution-workspace-source-roots.ts @@ -0,0 +1,164 @@ +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { eq } from "drizzle-orm"; +import { projectWorkspaces, executionWorkspaceRepositoryAccess, executionWorkspaces, type Db } from "@paperclipai/db"; +import { forbidden } from "../errors.js"; +import { localRepositoryPath } from "./local-repository-path.js"; + +const execFile = promisify(execFileCallback); +type DbTransaction = Parameters[0]>[0]; + +export async function canonicalRepositoryRoot(cwd: string): Promise { + const absolute = path.resolve(cwd); + try { return await fs.realpath(absolute); } + catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + const parent = path.dirname(absolute); + if (parent === absolute) throw error; + return path.join(await canonicalRepositoryRoot(parent), path.basename(absolute)); + } +} + +export function repositoryRootsOverlap(left: string, right: string): boolean { + const contains = (parent: string, child: string) => { + const relative = path.relative(parent, child); + return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)); + }; + return contains(left, right) || contains(right, left); +} + +export async function legacyRepositoryRootMayMatch(cwd: string, candidate: string): Promise { + const absolute = path.resolve(cwd); + let probe = absolute; + let suffix = ""; + let inaccessiblePrefix: string | null = null; + for (;;) { + try { + const canonical = await fs.realpath(probe); + if (repositoryRootsOverlap(path.join(canonical, suffix), candidate)) return true; + if (!inaccessiblePrefix) return false; + const unresolved = path.join(canonical, path.relative(probe, inaccessiblePrefix)); + return repositoryRootsOverlap(candidate, unresolved); + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (!["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP", "ENAMETOOLONG"].includes(code ?? "")) throw error; + if (code === "EACCES" || code === "EPERM") inaccessiblePrefix = probe; + const parent = path.dirname(probe); + if (parent === probe) return true; + suffix = path.join(path.basename(probe), suffix); + probe = parent; + } + } +} + +export async function configuredWorkspaceRoot(input: { cwd: string | null; repoUrl?: string | null }, scan: boolean): Promise { + const resolveDirectory = async (cwd: string): Promise => { + try { + const entry = await fs.stat(cwd); + if (!entry.isDirectory()) { + if (scan) return null; + throw forbidden("Workspace files cannot be detached from unverifiable provenance"); + } + return await fs.realpath(cwd); + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === "ENOENT" || (scan && ["ENOTDIR", "EACCES", "EPERM", "ELOOP", "ENAMETOOLONG"].includes(code ?? ""))) return null; + throw error; + } + }; + if (input.cwd) { + const cwd = await resolveDirectory(input.cwd); + if (cwd) return cwd; + } + let repositoryPath: string | null; + try { repositoryPath = input.repoUrl ? localRepositoryPath(input.repoUrl) : null; } + catch (error) { if (scan) return null; throw error; } + return repositoryPath ? resolveDirectory(repositoryPath) : null; +} + +export async function verifiedIsolatedWorktree(cwd: string, allowedOwnerRoots?: Set): Promise { + try { + const directory = await fs.lstat(cwd), marker = await fs.lstat(path.join(cwd, ".git")); + if (!directory.isDirectory() || directory.isSymbolicLink() || !marker.isFile() || marker.isSymbolicLink() || marker.size > 4096) return false; + const git = async (args: string[]) => (await execFile("git", ["-C", cwd, ...args], { + timeout: 30_000, env: { ...process.env, GIT_NO_LAZY_FETCH: "1" }, + })).stdout.trim(); + const checkoutRoot = await fs.realpath(await git(["rev-parse", "--show-toplevel"])); + if (checkoutRoot !== cwd) return false; + const commonRoot = await fs.realpath(path.resolve(checkoutRoot, await git(["rev-parse", "--git-common-dir"]))); + const gitRoot = await fs.realpath(await git(["rev-parse", "--absolute-git-dir"])); + if (gitRoot === commonRoot || (allowedOwnerRoots && !allowedOwnerRoots.has(commonRoot))) return false; + const registrations = (await git(["worktree", "list", "--porcelain", "-z"])).split("\0"); + return registrations.includes(`worktree ${checkoutRoot}`); + } catch { return false; } +} + +export async function unusedIsolatedCopyTarget(cwd: string): Promise { + const entry = await fs.lstat(cwd).catch((error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }); + if (!entry) return true; + return entry.isDirectory() && !entry.isSymbolicLink() && (await fs.readdir(cwd)).length === 0; +} + +export type WorkspaceSourceCopyKind = "linked_worktree" | "repository"; + +export async function isolatedWorkspaceSourceRoots(db: Db | DbTransaction, companyId: string, sourceCwd: string, copyKind: WorkspaceSourceCopyKind = "linked_worktree"): Promise<{ roots: string[]; commonRoot: string }> { + const git = async (cwd: string, ...args: string[]) => (await execFile("git", ["-C", cwd, ...args], { + timeout: 30_000, env: { ...process.env, GIT_NO_LAZY_FETCH: "1" }, + })).stdout.trim(); + const localRoot = await fs.realpath(sourceCwd); + if (copyKind === "repository" && await git(sourceCwd, "rev-parse", "--is-bare-repository") === "true") { + const gitRoot = await fs.realpath(path.resolve(sourceCwd, await git(sourceCwd, "rev-parse", "--absolute-git-dir"))); + return { roots: [...new Set([localRoot, gitRoot])], commonRoot: gitRoot }; + } + const checkoutRoot = await fs.realpath(path.resolve(await git(sourceCwd, "rev-parse", "--show-toplevel"))); + const commonRoot = await fs.realpath(path.resolve(checkoutRoot, await git(checkoutRoot, "rev-parse", "--git-common-dir"))); + const roots = new Set(copyKind === "repository" ? [localRoot, checkoutRoot, commonRoot] : [checkoutRoot, commonRoot]); + let primaryIdentified = await fs.realpath(await git(checkoutRoot, "rev-parse", "--absolute-git-dir")) === commonRoot; + const considerPrimary = async (candidatePath: string): Promise => { + const candidate = await fs.realpath(candidatePath).catch(() => null); + if (!candidate || candidate === checkoutRoot || candidate === commonRoot) return false; + try { + const candidateGitRoot = await fs.realpath(await git(candidate, "rev-parse", "--absolute-git-dir")); + if (candidateGitRoot !== commonRoot) return false; + const candidateCheckout = await fs.realpath(await git(candidate, "rev-parse", "--show-toplevel")); + const candidateCommon = await fs.realpath(path.resolve(candidate, await git(candidate, "rev-parse", "--git-common-dir"))); + const relative = path.relative(candidateCheckout, candidate); + if (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) + && candidateCommon === commonRoot) { + roots.add(candidateCheckout); + return true; + } + } catch { } + return false; + }; + if (!primaryIdentified) { + const registrations = (await git(checkoutRoot, "worktree", "list", "--porcelain", "-z")).split("\0"); + for (const registration of registrations) { + if (registration.startsWith("worktree ") && await considerPrimary(registration.slice("worktree ".length))) { + primaryIdentified = true; + break; + } + } + } + if (!primaryIdentified) { + const candidates = new Set(); + const configured = await db.select({ cwd: projectWorkspaces.cwd, repoUrl: projectWorkspaces.repoUrl }).from(projectWorkspaces) + .where(eq(projectWorkspaces.companyId, companyId)); + for (const workspace of configured) { + const cwd = await configuredWorkspaceRoot(workspace, true); + if (cwd) candidates.add(cwd); + } + const workspaces = await db.select({ cwd: executionWorkspaces.cwd }).from(executionWorkspaces).where(eq(executionWorkspaces.companyId, companyId)); + for (const workspace of workspaces) if (workspace.cwd) candidates.add(workspace.cwd); + const retained = await db.select({ cwd: executionWorkspaceRepositoryAccess.canonicalCwd }).from(executionWorkspaceRepositoryAccess) + .where(eq(executionWorkspaceRepositoryAccess.companyId, companyId)); + for (const entry of retained) candidates.add(entry.cwd); + for (const candidate of candidates) await considerPrimary(candidate); + } + return { roots: [...roots], commonRoot }; +} diff --git a/server/src/services/execution-workspaces.ts b/server/src/services/execution-workspaces.ts index c3bbb536196..9c4798614b8 100644 --- a/server/src/services/execution-workspaces.ts +++ b/server/src/services/execution-workspaces.ts @@ -1,15 +1,29 @@ +import { assertTaskWorkspaceAccess, assertTaskWorkspaceSourceProjectAccess } from "./task-workspace-source-access.js"; +import { parseObject } from "../adapters/utils.js"; +import { resolveCoreTrustPreset } from "./trust-preset-resolver.js"; +import { CONTROLLER_PROVISION_COPY_ONLY, workspaceRequiresCopyOnlyProvisioning } from "./workspace-host-provision.js"; +import { instanceSettingsService } from "./instance-settings.js"; +import { taskWorkspaceSelectableCondition } from "./task-workspace-selection.js"; +import { executionWorkspaceRepositoryService, lockExecutionWorkspaceRootProvenance, preserveExecutionWorkspaceRootProvenance, assertExecutionWorkspaceRootWriteAccess, assertExecutionWorkspaceRootReadAccess, assertIsolatedWorkspaceSourceReadAccess, retainIsolatedWorkspaceSourceAccess, canonicalRepositoryRoot, type IsolatedWorkspaceSourceAccess, type TaskWorkspaceCreationTrustContext } from "./execution-workspace-repositories.js"; +import { forbidden } from "../errors.js"; +import { taskWorkspaceSelectionSchema, type TaskWorkspaceSelection, type TaskWorkspaceIntent } from "@paperclipai/shared"; +import { authorizationService, executionWorkspaceReadSqlCondition, projectReadSqlCondition, issueReadSqlCondition, type AuthorizationActor } from "./authorization.js"; import type { SQL } from "drizzle-orm"; import { createWorkspaceGitInspectionCache } from "./workspace-git-inspection-cache.js"; import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; -import { promisify } from "node:util"; +import { isDeepStrictEqual, promisify } from "node:util"; import { and, asc, desc, eq, gt, inArray, isNull, lte, ne, or, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { executionWorkspaces, + agents, heartbeatRuns, + nativeRunFinalizations, + environmentLeases, + workspaceOperations, issueComments, issueWorkProducts, issues, @@ -43,7 +57,7 @@ import { normalizeIssueExecutionPolicy, parseIssueExecutionState, } from "./issue-execution-policy.js"; -import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js"; +import { buildExecutionWorkspaceAdapterConfig, resolveEffectiveWorkspaceStrategyType, parseProjectExecutionWorkspacePolicy, gateProjectExecutionWorkspacePolicy, parseIssueExecutionWorkspaceSettings, resolveExecutionWorkspaceMode, parseIssueAssigneeAdapterOverrides, applyDefaultIsolatedExecutionWorkspacePolicy } from "./execution-workspace-policy.js"; import { issueRecoveryActionService } from "./issue-recovery-actions.js"; import { logActivity } from "./activity-log.js"; import { @@ -64,6 +78,10 @@ import { selectConfiguredRuntimeServiceRows, } from "./workspace-runtime-read-model.js"; +export type TaskWorkspaceBindingPatch = Partial>; + type ExecutionWorkspaceRow = typeof executionWorkspaces.$inferSelect; type WorkspaceRuntimeServiceRow = typeof workspaceRuntimeServices.$inferSelect; type RuntimeServiceReadDb = Pick; @@ -217,6 +235,36 @@ async function acquireExecutionWorkspaceLifecycleLock( ); } +const workspaceSourceKeys = ["cwd", "projectId", "projectWorkspaceId", "sourceIssueId"] as const; + +async function preserveWorkspaceOwnershipForPatch( + tx: DbTransaction, current: typeof executionWorkspaces.$inferSelect, patch: Partial, +) { + if (workspaceSourceKeys.some(key => patch[key] !== undefined && patch[key] !== current[key])) { + await preserveExecutionWorkspaceRootProvenance(tx, { + companyId: current.companyId, workspaceId: current.id, cwd: current.cwd, + }); + } + const nextPatch = { ...patch }; + const retainedSources = { + ...parseObject(parseObject(patch.metadata)._issuePrivacySources), + ...parseObject(current.metadata?._issuePrivacySources), + }; + if (patch.sourceIssueId !== undefined && patch.sourceIssueId !== current.sourceIssueId && current.sourceIssueId) { + retainedSources[current.sourceIssueId] = true; + } + if (Object.keys(retainedSources).length > 0) { + nextPatch.metadata = { + ...(patch.metadata !== undefined ? patch.metadata ?? {} : current.metadata ?? {}), + _issuePrivacySources: retainedSources, + }; + } + if (current.metadata?.[CONTROLLER_PROVISION_COPY_ONLY] === true) { + nextPatch.metadata = { ...(nextPatch.metadata ?? current.metadata), [CONTROLLER_PROVISION_COPY_ONLY]: true }; + } + return nextPatch; +} + export type ReopenClosedIsolatedExecutionWorkspaceResult = // `generation` is the lifecycle generation the reopen published the active row // at. It owns the reopen-pending flag. A later clear must present this same @@ -1265,7 +1313,7 @@ async function loadEffectiveRuntimeServicesByExecutionWorkspace( } type WorkspaceOverviewPageRow = ExecutionWorkspaceRow & { - projectName: string; + projectName: string | null; projectWorkspaceMetadata: Record | null; }; @@ -1275,7 +1323,260 @@ type WorkspaceOverviewIssueRow = WorkspaceOverviewLinkedIssue & { const inspectGitForDisplay = createWorkspaceGitInspectionCache(inspectGitCloseReadiness); +async function hasPendingTaskWorkspaceRecovery(tx: DbTransaction, input: { + companyId: string; issueId: string; excludeRunId?: string; +}) { + const rows = await tx.select({ finalization: nativeRunFinalizations, run: heartbeatRuns }) + .from(nativeRunFinalizations).innerJoin(heartbeatRuns, and( + eq(heartbeatRuns.companyId, nativeRunFinalizations.companyId), eq(heartbeatRuns.id, nativeRunFinalizations.runId), + )).where(and(eq(nativeRunFinalizations.companyId, input.companyId), eq(nativeRunFinalizations.issueId, input.issueId), + input.excludeRunId ? ne(nativeRunFinalizations.runId, input.excludeRunId) : undefined)); + if (rows.length === 0) return false; + if (rows.some(({ finalization, run }) => finalization.leaseOwner || + !["committed", "terminal_failure"].includes(finalization.phase) || + !["succeeded", "interrupted", "failed", "cancelled", "timed_out"].includes(run.status))) return true; + const runIds = rows.map(({ run }) => run.id); + const [pendingLease] = await tx.select({ id: environmentLeases.id }).from(environmentLeases).where(and( + eq(environmentLeases.companyId, input.companyId), inArray(environmentLeases.heartbeatRunId, runIds), + or(and(isNull(environmentLeases.releasedAt), + sql`not coalesce(${environmentLeases.status} = 'retained' + and ${environmentLeases.leasePolicy} = 'reuse_by_environment' + and ${environmentLeases.cleanupStatus} = 'success', false)`), + eq(environmentLeases.status, "pending_cleanup"), eq(environmentLeases.cleanupStatus, "failed"), + sql`${environmentLeases.metadata} ? 'nativeWorkspaceExportResume'`), + )).limit(1); + if (pendingLease) return true; + const operations = await tx.select({ runId: workspaceOperations.heartbeatRunId, status: workspaceOperations.status }) + .from(workspaceOperations).where(and(eq(workspaceOperations.companyId, input.companyId), + inArray(workspaceOperations.heartbeatRunId, runIds), eq(workspaceOperations.phase, "workspace_finalize"))) + .orderBy(desc(workspaceOperations.createdAt), + asc(sql`case when ${workspaceOperations.status} = 'succeeded' then 1 else 0 end`)); + const latest = new Map(); + for (const operation of operations) { + if (operation.runId && !latest.has(operation.runId)) latest.set(operation.runId, operation.status); + } + return rows.some(({ finalization, run }) => { + const status = latest.get(run.id); + if (status !== undefined) return status !== "succeeded"; + return finalization.phase === "terminal_failure" && finalization.resultId !== null; + }); +} + export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServiceOptions = {}) { + async function resolveSourceWorkspaceStrategy(input: { + companyId: string; issueId?: string | null; assigneeAgentId?: string | null; + assigneeAdapterOverrides?: unknown; executionWorkspaceSettings?: unknown; + executionWorkspacePolicy: unknown; typedSelection?: boolean; projectId?: string | null; executionPolicy?: unknown; + }, reader: Db | DbTransaction = db) { + const [task] = input.issueId ? await reader.select().from(issues).where(and( + eq(issues.id, input.issueId), eq(issues.companyId, input.companyId), + )) : []; + const assigneeAgentId = task ? task.assigneeAgentId : input.assigneeAgentId; + const [agent] = assigneeAgentId ? await reader.select({ adapterConfig: agents.adapterConfig, permissions: agents.permissions }).from(agents) + .where(and(eq(agents.id, assigneeAgentId), eq(agents.companyId, input.companyId))) : []; + const overrides = assigneeAgentId ? parseIssueAssigneeAdapterOverrides(task ? task.assigneeAdapterOverrides : input.assigneeAdapterOverrides) : null; + const experimental = await instanceSettingsService(db).getExperimental(); + const projectPolicy = applyDefaultIsolatedExecutionWorkspacePolicy({ + projectPolicy: gateProjectExecutionWorkspacePolicy(parseProjectExecutionWorkspacePolicy(input.executionWorkspacePolicy), experimental.enableIsolatedWorkspaces), + defaultIsolatedWorkspacesEnabled: experimental.enableIsolatedWorkspaces && experimental.enableIsolatedWorkspacesByDefault, + hasProjectWorkspace: true, + }); + const settingsEnabled = experimental.enableIsolatedWorkspaces || input.typedSelection || task?.workspaceSelection || task?.executionWorkspaceId; + const issueSettings = settingsEnabled ? parseIssueExecutionWorkspaceSettings(input.executionWorkspaceSettings) : null; + const projectId = task ? task.projectId : input.projectId; + const [organization] = projectId ? await reader.select({ companyId: projects.companyId, executionWorkspacePolicy: projects.executionWorkspacePolicy }) + .from(projects).where(and(eq(projects.id, projectId), eq(projects.companyId, input.companyId))) : []; + const trust = resolveCoreTrustPreset({ companyId: input.companyId, agent, + project: organization, workspaceSourceProject: { companyId: input.companyId, executionWorkspacePolicy: input.executionWorkspacePolicy }, + issue: { companyId: input.companyId, executionPolicy: task ? task.executionPolicy : input.executionPolicy } }); + if (trust.kind === "denied") throw forbidden(trust.detail); + const resolvedMode = resolveExecutionWorkspaceMode({ projectPolicy, issueSettings, legacyUseProjectWorkspace: overrides?.useProjectWorkspace ?? null }); + const mode = trust.kind === "low_trust_review" && resolvedMode === "shared_workspace" ? "isolated_workspace" : resolvedMode; + const config = buildExecutionWorkspaceAdapterConfig({ agentConfig: parseObject(agent?.adapterConfig), projectPolicy, issueSettings, mode, + legacyUseProjectWorkspace: overrides?.useProjectWorkspace ?? null, adapterConfigOverrides: overrides?.adapterConfig }); + return resolveEffectiveWorkspaceStrategyType(mode, config); + } + + async function validateSelection(input: { taskCreation?: TaskWorkspaceCreationTrustContext; companyId: string; actor: AuthorizationActor; selection: TaskWorkspaceSelection; issueId?: string | null; parentIssueId?: string | null; assigneeAgentId?: string | null; assigneeUserId?: string | null; assigneeAdapterOverrides?: unknown; projectId?: string | null; executionPolicy?: unknown }, reader: Db | DbTransaction = db) { + const selection = taskWorkspaceSelectionSchema.parse(input.selection); + if (selection.kind === "existing") { + const [workspace] = await reader.select().from(executionWorkspaces).where(and( + eq(executionWorkspaces.id, selection.workspaceId), eq(executionWorkspaces.companyId, input.companyId), + taskWorkspaceSelectableCondition(), + await executionWorkspaceReadSqlCondition(reader, input.actor), + )); + if (!workspace) throw notFound("Workspace is unavailable or inaccessible"); + await assertTaskWorkspaceAccess(reader, input.actor, input.companyId, workspace.id, { write: true, issueId: input.issueId, parentIssueId: input.parentIssueId, assigneeAgentId: input.assigneeAgentId, assigneeUserId: input.assigneeUserId }); + if (workspace.cwd) await assertExecutionWorkspaceRootWriteAccess(reader, { + companyId: input.companyId, cwd: workspace.cwd, sourceWorkspaceId: workspace.id, actor: input.actor, taskCreation: input.taskCreation, + issueId: input.issueId, parentIssueId: input.parentIssueId, assigneeAgentId: input.assigneeAgentId, assigneeUserId: input.assigneeUserId, + }); + return { executionWorkspaceId: workspace.id, projectWorkspaceId: workspace.projectWorkspaceId, + executionWorkspacePreference: "reuse_existing", executionWorkspaceSettings: { mode: workspace.mode } }; + } + if (selection.kind === "configured_source") { + const [source] = await reader.select({ id: projectWorkspaces.id, projectId: projectWorkspaces.projectId, executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projectWorkspaces) + .innerJoin(projects, eq(projects.id, projectWorkspaces.projectId)).where(and( + eq(projectWorkspaces.id, selection.projectWorkspaceId), eq(projectWorkspaces.companyId, input.companyId), + await projectReadSqlCondition(reader, input.actor), + )); + if (!source) throw notFound("Workspace source is unavailable or inaccessible"); + const mode = selection.mode === "shared" ? "shared_workspace" : "isolated_workspace"; + const strategy = await resolveSourceWorkspaceStrategy({ ...input, typedSelection: true, + executionWorkspaceSettings: { mode }, executionWorkspacePolicy: source.executionWorkspacePolicy }, reader); + await assertTaskWorkspaceSourceProjectAccess(reader, input.actor, input.companyId, source.projectId, { + write: strategy !== "git_worktree", + issueId: input.issueId, parentIssueId: input.parentIssueId, + assigneeAgentId: input.assigneeAgentId, assigneeUserId: input.assigneeUserId, + }); + return { executionWorkspaceId: null, projectWorkspaceId: source.id, executionWorkspacePreference: null, + executionWorkspaceSettings: { mode: selection.mode === "shared" ? "shared_workspace" : "isolated_workspace" } }; + } + return { executionWorkspaceId: null, projectWorkspaceId: null, executionWorkspacePreference: null, + executionWorkspaceSettings: { mode: "shared_workspace" } }; + } + + async function inspectTaskWorkspace(companyId: string, issueId: string, actor: AuthorizationActor) { + const [task] = await db.select().from(issues).where(and(eq(issues.id, issueId), eq(issues.companyId, companyId), await issueReadSqlCondition(db, actor))); + if (!task) throw notFound("Task not found"); + const [workspace] = task.executionWorkspaceId ? await db.select().from(executionWorkspaces) + .where(and(eq(executionWorkspaces.id, task.executionWorkspaceId), eq(executionWorkspaces.companyId, companyId), await executionWorkspaceReadSqlCondition(db, actor))) : []; + if (task.executionWorkspaceId && !workspace) throw notFound("Workspace is unavailable or inaccessible"); + if (workspace) await assertTaskWorkspaceAccess(db, actor, companyId, workspace.id); + return { issueId, bindingRevision: task.workspaceBindingRevision, selection: task.workspaceSelection, + pendingSelection: task.workspacePendingSelection, workspace: workspace ? toExecutionWorkspace(workspace) : null }; + } + + async function selectTaskWorkspace(input: { companyId: string; issueId: string; actor: AuthorizationActor; + selection: TaskWorkspaceSelection; expectedBindingRevision: number; requestKey: string }) { + return db.transaction(async tx => { + const [task] = await tx.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId), + await issueReadSqlCondition(tx, input.actor))).for("update"); + if (!task) throw notFound("Task not found"); + const decision = await authorizationService(tx).decide({ actor: input.actor, action: "issue:mutate", resource: { + type: "issue", companyId: task.companyId, issueId: task.id, + status: task.status, assigneeAgentId: task.assigneeAgentId, assigneeUserId: task.assigneeUserId, + } }); + if (!decision.allowed) throw forbidden("Task workspace modification is not allowed"); + if (task.workspaceSelection?.request?.key === input.requestKey) { + if (task.workspaceSelection.request.expectedBindingRevision !== input.expectedBindingRevision || + JSON.stringify(taskWorkspaceSelectionSchema.parse(task.workspaceSelection.selection)) !== JSON.stringify(taskWorkspaceSelectionSchema.parse(input.selection))) { + throw conflict("Workspace selection request key was already used for different intent"); + } + return { kind: "applied" as const, bindingRevision: task.workspaceBindingRevision }; + } + if (task.workspacePendingSelection?.requestKey === input.requestKey) { + if ((task.workspacePendingSelection.intent.request?.expectedBindingRevision ?? task.workspacePendingSelection.expectedBindingRevision) !== input.expectedBindingRevision || + JSON.stringify(taskWorkspaceSelectionSchema.parse(task.workspacePendingSelection.intent.selection)) !== JSON.stringify(taskWorkspaceSelectionSchema.parse(input.selection))) { + throw conflict("Workspace selection request key was already used for different intent"); + } + return { kind: "scheduled" as const, applies: "next_normal_admission" as const, bindingRevision: task.workspaceBindingRevision }; + } + if (task.workspaceBindingRevision !== input.expectedBindingRevision) throw conflict("Task workspace binding changed; inspect it before selecting again"); + await validateSelection(input, tx); + const intent: TaskWorkspaceIntent = { version: 1, request: { key: input.requestKey, expectedBindingRevision: input.expectedBindingRevision }, selection: input.selection, source: "explicit" }; + const nextRevision = task.workspaceBindingRevision + 1; + await tx.update(issues).set({ workspaceBindingRevision: nextRevision, + workspacePendingSelection: { version: 1, requestKey: input.requestKey, + expectedBindingRevision: nextRevision, intent }, updatedAt: new Date() }).where(eq(issues.id, task.id)); + return { kind: "scheduled" as const, applies: "next_normal_admission" as const, bindingRevision: nextRevision }; + }); + } + + async function applyPendingTaskWorkspaceSelection(input: { companyId: string; issueId: string; runId: string; actor: AuthorizationActor }) { + return db.transaction(async tx => { + const [task] = await tx.select().from(issues).where(and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId))).for("update"); + if (!task?.workspacePendingSelection) return false; + const pending = task.workspacePendingSelection; + if (pending.expectedBindingRevision !== task.workspaceBindingRevision) throw conflict("Pending workspace selection is stale"); + const [holder] = await tx.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, input.companyId), ne(heartbeatRuns.id, input.runId), + or(eq(heartbeatRuns.nativeIssueId, input.issueId), + sql`coalesce(${heartbeatRuns.contextSnapshot}->>'issueId', ${heartbeatRuns.contextSnapshot}->>'taskId') = ${input.issueId}`), + eq(heartbeatRuns.status, "running"), + )).limit(1); + if (holder) throw conflict("Previous task run must finish persistence before changing its workspace"); + const unsettled = await hasPendingTaskWorkspaceRecovery(tx, { ...input, excludeRunId: input.runId }); + if (unsettled) throw conflict("Previous task run still owns workspace recovery; finish recovery before changing its workspace"); + const patch = await validateSelection({ ...input, selection: pending.intent.selection }, tx); + await tx.update(issues).set({ ...patch, workspaceSelection: pending.intent, workspacePendingSelection: null, + workspaceBindingRevision: task.workspaceBindingRevision + 1, updatedAt: new Date() }).where(eq(issues.id, task.id)); + return true; + }); + } + + async function bindTaskWorkspace(companyId: string, issueId: string, workspaceId: string, patch: TaskWorkspaceBindingPatch = {}) { + return db.transaction(async tx => { + const [task] = await tx.select().from(issues).where(and(eq(issues.id, issueId), eq(issues.companyId, companyId))).for("no key update"); + const [workspace] = await tx.select().from(executionWorkspaces).where(and(eq(executionWorkspaces.id, workspaceId), eq(executionWorkspaces.companyId, companyId))).for("no key update"); + if (!task || !workspace) throw notFound("Task workspace unavailable"); + await tx.update(executionWorkspaces).set({ metadata: { ...workspace.metadata, + _issuePrivacySources: { ...(workspace.metadata?._issuePrivacySources as Record ?? {}), [task.id]: true } } }).where(eq(executionWorkspaces.id, workspaceId)); + const nextRevision = task.workspaceBindingRevision + (task.executionWorkspaceId === workspaceId ? 0 : 1); + const pending = task.workspacePendingSelection; + const pendingAfterFirstBinding = !task.executionWorkspaceId && pending?.expectedBindingRevision === task.workspaceBindingRevision + ? { ...pending, expectedBindingRevision: nextRevision } + : pending; + const [bound] = await tx.update(issues).set({ ...patch, executionWorkspaceId: workspaceId, + workspaceBindingRevision: nextRevision, workspacePendingSelection: pendingAfterFirstBinding, updatedAt: new Date() }) + .where(eq(issues.id, issueId)).returning(); + return bound; + }); + } + + async function assertTaskWorkspaceUpdateAccess(input: { + task: typeof issues.$inferSelect; actor: AuthorizationActor; + }, tx: DbTransaction) { + const { task, actor } = input; + if (task.executionWorkspaceId) { + await assertTaskWorkspaceAccess(tx, actor, task.companyId, task.executionWorkspaceId, { write: true, issueId: task.id }); + return; + } + if (!task.projectWorkspaceId) return; + const [source] = await tx.select({ projectId: projectWorkspaces.projectId, executionWorkspacePolicy: projects.executionWorkspacePolicy }) + .from(projectWorkspaces).innerJoin(projects, eq(projects.id, projectWorkspaces.projectId)) + .where(and(eq(projectWorkspaces.id, task.projectWorkspaceId), eq(projectWorkspaces.companyId, task.companyId), + await projectReadSqlCondition(tx, actor))); + if (!source) throw notFound("Workspace source is unavailable or inaccessible"); + const strategy = await resolveSourceWorkspaceStrategy({ companyId: task.companyId, issueId: task.id, + executionWorkspaceSettings: task.executionWorkspaceSettings, executionWorkspacePolicy: source.executionWorkspacePolicy }, tx); + await assertTaskWorkspaceSourceProjectAccess(tx, actor, task.companyId, source.projectId, { + issueId: task.id, write: strategy !== "git_worktree", + }); + } + + async function prepareTaskWorkspaceUpdate(input: { + companyId: string; issueId: string; patch: TaskWorkspaceBindingPatch; + }, tx: DbTransaction) { + const [task] = await tx.select().from(issues).where(and( + eq(issues.id, input.issueId), eq(issues.companyId, input.companyId), + )).for("no key update"); + if (!task) throw notFound("Task not found"); + const fields = ["executionWorkspaceId", "projectWorkspaceId", "executionWorkspacePreference", "executionWorkspaceSettings"] as const; + if (!fields.some(field => input.patch[field] !== undefined && !isDeepStrictEqual(input.patch[field], task[field]))) return {}; + if (input.patch.projectWorkspaceId !== undefined && input.patch.projectWorkspaceId !== task.projectWorkspaceId && + task.executionWorkspaceId && input.patch.executionWorkspaceId !== null) { + throw conflict("Task already has bound files; schedule a workspace selection or explicitly clear its binding when changing the source"); + } + const [holder] = await tx.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, input.companyId), eq(heartbeatRuns.status, "running"), + or(eq(heartbeatRuns.nativeIssueId, input.issueId), + sql`coalesce(${heartbeatRuns.contextSnapshot}->>'issueId', ${heartbeatRuns.contextSnapshot}->>'taskId') = ${input.issueId}`, + task.executionRunId ? eq(heartbeatRuns.id, task.executionRunId) : undefined), + )).limit(1); + const unsettled = await hasPendingTaskWorkspaceRecovery(tx, input); + if (holder || unsettled) throw conflict("Task run still owns its workspace; schedule a workspace selection for the next admission"); + const [selectedWorkspace] = input.patch.executionWorkspaceId + ? await tx.select({ projectWorkspaceId: executionWorkspaces.projectWorkspaceId }).from(executionWorkspaces).where(and( + eq(executionWorkspaces.id, input.patch.executionWorkspaceId), eq(executionWorkspaces.companyId, input.companyId), + )).limit(1) + : []; + if (input.patch.executionWorkspaceId && !selectedWorkspace) throw notFound("Execution workspace not found"); + return { workspaceBindingRevision: task.workspaceBindingRevision + 1, + workspacePendingSelection: null, workspaceSelection: null, + ...(selectedWorkspace ? { projectWorkspaceId: selectedWorkspace.projectWorkspaceId } : {}) }; + } + const inspectDisplay = opts.inspectGitCloseReadiness ? createWorkspaceGitInspectionCache(opts.inspectGitCloseReadiness) : inspectGitForDisplay; @@ -1723,7 +2024,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic db .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy }) .from(projects) - .where(and(eq(projects.companyId, workspace.companyId), eq(projects.id, workspace.projectId))) + .where(and(eq(projects.companyId, workspace.companyId), workspace.projectId ? eq(projects.id, workspace.projectId) : sql`false`)) .then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy)), ]); const config = readExecutionWorkspaceConfig((workspace.metadata as Record | null) ?? null); @@ -1833,6 +2134,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic issueId?: string; status?: string; reuseEligible?: boolean; + selectableForTask?: boolean; readCondition?: SQL; }, ) { @@ -1848,6 +2150,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic if (statuses.length === 1) conditions.push(eq(executionWorkspaces.status, statuses[0]!)); else if (statuses.length > 1) conditions.push(inArray(executionWorkspaces.status, statuses)); } + if (filters?.selectableForTask) conditions.push(taskWorkspaceSelectableCondition()); if (filters?.reuseEligible) { conditions.push(inArray(executionWorkspaces.status, ["active", "idle", "in_review"])); conditions.push(isNull(executionWorkspaces.closedAt)); @@ -1869,6 +2172,13 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic } return { + requestTaskRepository: executionWorkspaceRepositoryService(db).request, + listTaskRepositories: executionWorkspaceRepositoryService(db).list, + prepareTaskRepositoriesForAdmission: executionWorkspaceRepositoryService(db).prepareForAdmission, + guardTaskRepositoryRootForPublication: executionWorkspaceRepositoryService(db).guardForPublication, + prepareConfiguredRepositorySource: executionWorkspaceRepositoryService(db).prepareConfiguredRepositorySource, + reserveTaskRepositoryRootForRecovery: executionWorkspaceRepositoryService(db).reserveForRecovery, + resolveSourceWorkspaceStrategy, validateSelection, inspectTaskWorkspace, selectTaskWorkspace, applyPendingTaskWorkspaceSelection, bindTaskWorkspace, prepareTaskWorkspaceUpdate, assertTaskWorkspaceUpdateAccess, listOverview: async ( companyId: string, filters: WorkspaceOverviewQuery, @@ -1876,13 +2186,13 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic ): Promise => { const conditions = buildOverviewConditions(companyId, filters); if (readCondition) conditions.push(readCondition); - const whereClause = and(...conditions); + const whereClause = and(...conditions, sql`(${executionWorkspaces.projectId} IS NULL OR ${projects.id} IS NOT NULL)`); const [totalRow, rows] = await Promise.all([ db .select({ count: sql`count(*)::int` }) .from(executionWorkspaces) - .innerJoin( + .leftJoin( projects, and( eq(projects.id, executionWorkspaces.projectId), @@ -1921,7 +2231,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic projectWorkspaceMetadata: projectWorkspaces.metadata, }) .from(executionWorkspaces) - .innerJoin( + .leftJoin( projects, and( eq(projects.id, executionWorkspaces.projectId), @@ -2042,7 +2352,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic workspaceId: row.id, workspaceName: row.name, projectId: row.projectId, - projectUrlKey: deriveProjectUrlKey(row.projectName, row.projectId), + projectUrlKey: row.projectId && row.projectName ? deriveProjectUrlKey(row.projectName, row.projectId) : null, projectName: row.projectName, mode: row.mode as WorkspaceOverviewItem["mode"], strategyType: row.strategyType as WorkspaceOverviewItem["strategyType"], @@ -2086,6 +2396,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic issueId?: string; status?: string; reuseEligible?: boolean; + selectableForTask?: boolean; readCondition?: SQL; }) => { const conditions = buildListConditions(companyId, filters); @@ -2113,6 +2424,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic issueId?: string; status?: string; reuseEligible?: boolean; + selectableForTask?: boolean; readCondition?: SQL; }) => { const conditions = buildListConditions(companyId, filters); @@ -2900,52 +3212,146 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic }, update: async (id: string, patch: Partial) => { - const row = await db - .update(executionWorkspaces) - .set({ ...patch, updatedAt: new Date() }) - .where(eq(executionWorkspaces.id, id)) - .returning() - .then((rows) => rows[0] ?? null); - return row ? toExecutionWorkspace(row) : null; + if (patch.metadata === undefined && !workspaceSourceKeys.some(key => patch[key] !== undefined)) { + const row = await db.update(executionWorkspaces).set({ ...patch, updatedAt: new Date() }) + .where(eq(executionWorkspaces.id, id)).returning().then(rows => rows[0] ?? null); + return row ? toExecutionWorkspace(row) : null; + } + const [identity] = await db.select({ companyId: executionWorkspaces.companyId }).from(executionWorkspaces) + .where(eq(executionWorkspaces.id, id)); + if (!identity) return null; + return db.transaction(async tx => { + await lockExecutionWorkspaceRootProvenance(tx, identity.companyId); + const [current] = await tx.select().from(executionWorkspaces).where(and( + eq(executionWorkspaces.id, id), eq(executionWorkspaces.companyId, identity.companyId), + )).for("no key update"); + if (!current) return null; + const nextPatch = await preserveWorkspaceOwnershipForPatch(tx, current, patch); + const [row] = await tx.update(executionWorkspaces).set({ ...nextPatch, updatedAt: new Date() }) + .where(and(eq(executionWorkspaces.id, id), eq(executionWorkspaces.companyId, current.companyId))).returning(); + return row ? toExecutionWorkspace(row) : null; + }); }, // Reopen one closed isolated execution workspace so an authorized issue can // use it again. The caller must first authorize the request on the issue. - // The whole operation runs under the per-workspace lifecycle lock: it reads - // the row in the issue scope, rebuilds the worktree, and only then publishes - // the row as "active". A rebuild failure keeps the row closed and returns an - // error, so the caller never dispatches a run against a broken workspace. reopenClosedIsolatedExecutionWorkspaceForIssue: async (input: { workspaceId: string; issue: { id: string; companyId: string; projectId: string | null }; actor: { agentId: string | null; actorType: string }; + authorizationActor: AuthorizationActor; }): Promise => { const { issue, actor } = input; - // Bind the workspace to the issue company and project. A null project on - // the issue must match a null project on the row (IS NOT DISTINCT FROM). - const projectIdCondition = - issue.projectId == null - ? sql`${executionWorkspaces.projectId} IS NULL` - : eq(executionWorkspaces.projectId, issue.projectId); - - return db.transaction(async (tx): Promise => { + const [boundSnapshot] = await db.select({ id: issues.id }).from(issues).where(and( + eq(issues.id, issue.id), eq(issues.companyId, issue.companyId), + eq(issues.executionWorkspaceId, input.workspaceId), + await issueReadSqlCondition(db, input.authorizationActor), + )); + const snapshot = boundSnapshot ? await db.select().from(executionWorkspaces).where(and( + eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, issue.companyId), + eq(executionWorkspaces.mode, "isolated_workspace"), + await executionWorkspaceReadSqlCondition(db, input.authorizationActor), + )).then(rows => rows[0] ?? null) : null; + if (!snapshot) return { ok: false, code: "not_reopenable", message: "Execution workspace is not reopenable" }; + await assertTaskWorkspaceAccess(db, input.authorizationActor, issue.companyId, snapshot.id); + const [ + { ensurePersistedExecutionWorkspaceAvailable }, + { workspaceOperationService }, + { ensureManagedProjectWorkspace }, + ] = await Promise.all([ + import("./workspace-runtime.js"), + import("./workspace-operations.js"), + import("./heartbeat.js"), + ]); + const [projectWorkspace, projectPolicy] = await Promise.all([ + snapshot.projectWorkspaceId + ? db + .select({ cwd: projectWorkspaces.cwd }) + .from(projectWorkspaces) + .where(and( + eq(projectWorkspaces.companyId, snapshot.companyId), + eq(projectWorkspaces.id, snapshot.projectWorkspaceId), + )) + .then((rows) => rows[0] ?? null) + : null, + db + .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy }) + .from(projects) + .where(and(eq(projects.companyId, snapshot.companyId), snapshot.projectId ? eq(projects.id, snapshot.projectId) : sql`false`)) + .then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy)), + ]); + let resolvedBaseCwd = projectWorkspace?.cwd ?? null; + if (isClosedExecutionWorkspaceStatus(snapshot.status) && resolvedBaseCwd == null && snapshot.strategyType === "git_worktree" && snapshot.projectId) { + try { + const managedWorkspace = await ensureManagedProjectWorkspace({ + companyId: snapshot.companyId, + projectId: snapshot.projectId, + repoUrl: snapshot.repoUrl, + prepareLocalSource: async sourceCwd => { + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { + companyId: snapshot.companyId, sourceCwd, actor: input.authorizationActor, copyKind: "repository", + }); + return targetCwd => retainIsolatedWorkspaceSourceAccess(db, { + companyId: snapshot.companyId, sourceCwd, targetCwd, actor: input.authorizationActor, + captured, issueId: issue.id, copyKind: "repository", + }); + }, + resolveGitAuth: createGitRemoteAuthProvider(db, snapshot.companyId, { + issueId: snapshot.sourceIssueId ?? issue.id, + }), + }); + resolvedBaseCwd = managedWorkspace.cwd; + } catch { + return { ok: false, code: "rebuild_failed", message: "Failed to rebuild the execution workspace" }; + } + } + let capturedSource: IsolatedWorkspaceSourceAccess | null = null; + let authorizedTarget: string | null = null; + if (isClosedExecutionWorkspaceStatus(snapshot.status) && snapshot.strategyType === "git_worktree") { + try { + if (!resolvedBaseCwd || !(snapshot.providerRef ?? snapshot.cwd)) throw new Error("Missing workspace copy identity"); + authorizedTarget = await canonicalRepositoryRoot(snapshot.providerRef ?? snapshot.cwd!); + if (snapshot.cwd && await canonicalRepositoryRoot(snapshot.cwd) !== authorizedTarget) { + throw new Error("Workspace checkout and provider roots disagree"); + } + capturedSource = await assertIsolatedWorkspaceSourceReadAccess(db, { + companyId: snapshot.companyId, sourceCwd: resolvedBaseCwd, actor: input.authorizationActor, + }); + await retainIsolatedWorkspaceSourceAccess(db, { + companyId: snapshot.companyId, sourceCwd: resolvedBaseCwd, targetCwd: authorizedTarget, + actor: input.authorizationActor, captured: capturedSource, issueId: issue.id, phase: "preparing", + }); + } catch { + return { ok: false, code: "rebuild_failed", message: "Failed to rebuild the execution workspace" }; + } + } + const result = await db.transaction(async (tx): Promise => { await acquireExecutionWorkspaceLifecycleLock(tx, input.workspaceId); + const [boundTask] = await tx.select().from(issues).where(and( + eq(issues.id, issue.id), eq(issues.companyId, issue.companyId), + eq(issues.executionWorkspaceId, input.workspaceId), + await issueReadSqlCondition(tx, input.authorizationActor), + )); + if (!boundTask) return { ok: false, code: "not_reopenable", message: "Execution workspace is not reopenable" }; const row = await tx .select() .from(executionWorkspaces) .where(and( eq(executionWorkspaces.id, input.workspaceId), eq(executionWorkspaces.companyId, issue.companyId), - projectIdCondition, + await executionWorkspaceReadSqlCondition(tx, input.authorizationActor), eq(executionWorkspaces.mode, "isolated_workspace"), )) .then((rows) => rows[0] ?? null); if (!row) { - // Wrong company, wrong project, wrong mode, or missing. Fail closed and // disclose no workspace detail. return { ok: false, code: "not_reopenable", message: "Execution workspace is not reopenable" }; } + await assertTaskWorkspaceAccess(tx, input.authorizationActor, issue.companyId, row.id); if (!isClosedExecutionWorkspaceStatus(row.status)) { + if (row.cwd) await assertExecutionWorkspaceRootReadAccess(tx, { + companyId: row.companyId, cwd: row.cwd, actor: input.authorizationActor, + }); // A concurrent reopen already restored the row. Report success without a // second rebuild so the caller continues normally. The other request // owns the reopen-pending flag, so return its generation and let the @@ -2958,53 +3364,27 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic }; } - const [ - { ensurePersistedExecutionWorkspaceAvailable }, - { workspaceOperationService }, - { ensureManagedProjectWorkspace }, - ] = await Promise.all([ - import("./workspace-runtime.js"), - import("./workspace-operations.js"), - // heartbeat.js imports this module, so a static import creates a - // cycle. Load ensureManagedProjectWorkspace dynamically instead. - import("./heartbeat.js"), - ]); - const [projectWorkspace, projectPolicy] = await Promise.all([ - row.projectWorkspaceId - ? db - .select({ cwd: projectWorkspaces.cwd }) - .from(projectWorkspaces) - .where(and( - eq(projectWorkspaces.companyId, row.companyId), - eq(projectWorkspaces.id, row.projectWorkspaceId), - )) - .then((rows) => rows[0] ?? null) - : null, - db - .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy }) - .from(projects) - .where(and(eq(projects.companyId, row.companyId), eq(projects.id, row.projectId))) - .then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy)), - ]); - // Resolve the base checkout that the rebuild spawns git in. A - // local-folder project stores its base path in projectWorkspaces.cwd. - // A managed_checkout project stores null there, so resolve its live - // managed checkout instead. Never use row.cwd for a git_worktree - // rebuild: row.cwd is the archived worktree path, which the reaper - // already removed from disk. A spawn in that missing directory fails - // with "spawn git ENOENT" and hides the real cause. - let resolvedBaseCwd = projectWorkspace?.cwd ?? null; - if (resolvedBaseCwd == null && row.strategyType === "git_worktree" && row.projectId) { - const managedWorkspace = await ensureManagedProjectWorkspace({ - companyId: row.companyId, - projectId: row.projectId, - repoUrl: row.repoUrl, - resolveGitAuth: createGitRemoteAuthProvider(db, row.companyId, { - issueId: row.sourceIssueId ?? issue.id, - }), - }); - resolvedBaseCwd = managedWorkspace.cwd; + if (capturedSource && !["projectId", "projectWorkspaceId", "cwd", "providerRef", "repoUrl", "baseRef", "branchName", "strategyType", "mode", "metadata"] + .every(key => isDeepStrictEqual(row[key as keyof typeof row], snapshot[key as keyof typeof snapshot]))) { + return { ok: false, code: "not_reopenable", message: "Execution workspace source changed during reopen" }; } + if (capturedSource && snapshot.projectWorkspaceId) { + const [currentSource] = await tx.select({ cwd: projectWorkspaces.cwd }).from(projectWorkspaces) + .where(and(eq(projectWorkspaces.id, snapshot.projectWorkspaceId), eq(projectWorkspaces.companyId, snapshot.companyId))); + if (!isDeepStrictEqual(currentSource ?? null, projectWorkspace)) { + return { ok: false, code: "not_reopenable", message: "Execution workspace source changed during reopen" }; + } + } + const verifyCopyIdentity = capturedSource && authorizedTarget ? async (sourceCwd: string, targetCwd: string) => { + if (!capturedSource!.roots.includes(await fs.realpath(sourceCwd)) + || await canonicalRepositoryRoot(targetCwd) !== authorizedTarget) { + throw forbidden("Execution workspace copy identity changed during reopen"); + } + for (const cwd of [...capturedSource!.roots, authorizedTarget!]) await assertExecutionWorkspaceRootReadAccess(tx, { + companyId: row.companyId, cwd, actor: input.authorizationActor, + }); + } : undefined; + const config = readExecutionWorkspaceConfig(row.metadata as Record | null); const nextGeneration = readExecutionWorkspaceLifecycleGeneration( row.metadata as Record | null, @@ -3019,8 +3399,38 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic let rebuildError: string | null = null; try { + const [assignee, organization, sourceProject] = await Promise.all([ + boundTask.assigneeAgentId ? tx.select({ companyId: agents.companyId, permissions: agents.permissions }).from(agents) + .where(and(eq(agents.id, boundTask.assigneeAgentId), eq(agents.companyId, row.companyId))) + .then(rows => rows[0] ?? null) : null, + boundTask.projectId ? tx.select({ companyId: projects.companyId, executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projects) + .where(and(eq(projects.id, boundTask.projectId), eq(projects.companyId, row.companyId))) + .then(rows => rows[0] ?? null) : null, + row.projectId ? tx.select({ companyId: projects.companyId, executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projects) + .where(and(eq(projects.id, row.projectId), eq(projects.companyId, row.companyId))) + .then(rows => rows[0] ?? null) : row.projectWorkspaceId + ? tx.select({ companyId: projects.companyId, executionWorkspacePolicy: projects.executionWorkspacePolicy }) + .from(projectWorkspaces).innerJoin(projects, eq(projects.id, projectWorkspaces.projectId)) + .where(and(eq(projectWorkspaces.id, row.projectWorkspaceId), eq(projectWorkspaces.companyId, row.companyId), eq(projects.companyId, row.companyId))) + .then(rows => rows[0] ?? null) : null, + ]); + if ((boundTask.assigneeAgentId && !assignee) || (boundTask.projectId && !organization) + || ((row.projectId || row.projectWorkspaceId) && !sourceProject)) { + throw forbidden("Execution workspace trust policy is unavailable"); + } + const trust = resolveCoreTrustPreset({ companyId: row.companyId, agent: assignee, issue: boundTask, + project: organization, workspaceSourceProject: sourceProject }); + if (trust.kind === "denied") throw forbidden(trust.detail); + const copyOnlyProvisioning = await workspaceRequiresCopyOnlyProvisioning(tx, { + companyId: row.companyId, task: boundTask, sourceProjectId: row.projectId, + sourceWorkspaceId: row.projectWorkspaceId, boundWorkspace: row, + }); + if (copyOnlyProvisioning) nextMetadata[CONTROLLER_PROVISION_COPY_ONLY] = true; const realized = await ensurePersistedExecutionWorkspaceAvailable({ db: tx as unknown as Db, + skipHostProvision: trust.kind === "low_trust_review" || copyOnlyProvisioning, + beforeSourceCopy: verifyCopyIdentity, + afterSourceCopy: verifyCopyIdentity, base: { baseCwd: resolvedBaseCwd ?? row.cwd ?? "", source: "task_session", @@ -3166,7 +3576,26 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic }, }); return { ok: true, reopened: true, workspace: toExecutionWorkspace(activeRow), generation: nextGeneration }; + }).catch((error): ReopenClosedIsolatedExecutionWorkspaceResult => { + logger.warn({ error, executionWorkspaceId: snapshot.id }, "Execution workspace reopen failed"); + return { ok: false, code: "rebuild_failed", message: "Failed to rebuild the execution workspace" }; }); + if (capturedSource && authorizedTarget && resolvedBaseCwd) { + try { + await retainIsolatedWorkspaceSourceAccess(db, { + companyId: snapshot.companyId, sourceCwd: resolvedBaseCwd, targetCwd: authorizedTarget, + actor: input.authorizationActor, captured: capturedSource, issueId: issue.id, phase: "verified", + }); + } catch { + if (result.ok && result.reopened) { + await executionWorkspaceService(db).clearReopenPendingConsumptionForUnconsumedReopen({ + workspaceId: snapshot.id, issue, actor, expectedGeneration: result.generation, + }); + } + return { ok: false, code: "rebuild_failed", message: "Failed to rebuild the execution workspace" }; + } + } + return result; }, // Clear the reopen-pending flag after a caller failed to consume a reopened @@ -3263,12 +3692,17 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic | { outcome: "reopen_pending" } | null > => { + const [identity] = await db.select({ companyId: executionWorkspaces.companyId }).from(executionWorkspaces) + .where(eq(executionWorkspaces.id, input.id)); + if (!identity) return null; return db.transaction(async (tx) => { + await lockExecutionWorkspaceRootProvenance(tx, identity.companyId); await acquireExecutionWorkspaceLifecycleLock(tx, input.id); const fresh = await tx .select() .from(executionWorkspaces) - .where(eq(executionWorkspaces.id, input.id)) + .where(and(eq(executionWorkspaces.id, input.id), eq(executionWorkspaces.companyId, identity.companyId))) + .for("no key update") .then((rows) => rows[0] ?? null); if (!fresh || isClosedExecutionWorkspaceStatus(fresh.status)) { // The row is missing or already closed by a concurrent path. Do not @@ -3282,8 +3716,9 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic // rebuilt worktree. The route maps this to HTTP 409. return { outcome: "reopen_pending" }; } + const nextPatch = await preserveWorkspaceOwnershipForPatch(tx, fresh, input.patch); const baseMetadata = - (input.patch.metadata as Record | null | undefined) + (nextPatch.metadata as Record | null | undefined) ?? (fresh.metadata as Record | null); const archiveMetadata = clearMetadataReopenPendingConsumption( bumpExecutionWorkspaceLifecycleGeneration(baseMetadata), @@ -3291,7 +3726,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic const archived = await tx .update(executionWorkspaces) .set({ - ...input.patch, + ...nextPatch, status: "archived", closedAt: input.closedAt, cleanupReason: null, @@ -3300,6 +3735,7 @@ export function executionWorkspaceService(db: Db, opts: ExecutionWorkspaceServic }) .where(and( eq(executionWorkspaces.id, input.id), + eq(executionWorkspaces.companyId, fresh.companyId), // Defense in depth: never archive a reopen-pending row even if the // flag appears between the read above and this write. The lifecycle // lock already serializes reopen and archive, so this predicate only diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index db99e407a38..c8df58d270a 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -19,6 +19,7 @@ import { createHeartbeatScheduling, formatIssueIdentifierLink, } from "./heartbeat/scheduling.js"; +import { canApplyTaskWorkspaceSelectionAtAdmission, taskWorkspaceRuntimeSelectionEnabled } from "./execution-workspace-policy.js"; import { agentExecutionsHaveStopped } from "./agent-execution-stop.js"; import { cancelHeartbeatNativeRun, @@ -147,7 +148,6 @@ export { mergeExecutionWorkspaceMetadataForPersistence, resolveExecutionWorkspaceBranchOwnership, stripWorkspaceRuntimeFromExecutionRunConfig, - stripHostWorkspaceProvisionForLowTrustSandbox, preflightLowTrustWorkspaceIsolation, resolveWorkspaceAfterLowTrustPreflight, ensureManagedProjectWorkspace, @@ -341,6 +341,7 @@ import { resolveHeartbeatNativeRuntimeMode, } from "./native-runtime/index.js"; +import { nativeChatWorkspaceCwd } from "./native-runtime/native-chat-workspace.js"; import { type NativeExecutionInput, type NativeSessionBackend, @@ -431,6 +432,8 @@ import { } from "./run-scratch.js"; import { applyDefaultIsolatedExecutionWorkspacePolicy, + buildExecutionWorkspaceAdapterConfig, + parseIssueAssigneeAdapterOverrides, gateProjectExecutionWorkspacePolicy, parseIssueExecutionWorkspaceSettings, parseProjectExecutionWorkspacePolicy, @@ -616,33 +619,10 @@ export { resolveReusableSandboxLifecycle } from "./heartbeat/runtime-selection.j export { resolveNativeSandboxLifecycle } from "./heartbeat/runtime-selection.js"; -interface ParsedIssueAssigneeAdapterOverrides { - adapterConfig: Record | null; - useProjectWorkspace: boolean | null; -} - function readNonEmptyString(value: unknown): string | null { return typeof value === "string" && value.trim().length > 0 ? value : null; } -function parseIssueAssigneeAdapterOverrides( - raw: unknown, -): ParsedIssueAssigneeAdapterOverrides | null { - const parsed = parseObject(raw); - const parsedAdapterConfig = parseObject(parsed.adapterConfig); - const adapterConfig = - Object.keys(parsedAdapterConfig).length > 0 ? parsedAdapterConfig : null; - const useProjectWorkspace = - typeof parsed.useProjectWorkspace === "boolean" - ? parsed.useProjectWorkspace - : null; - if (!adapterConfig && useProjectWorkspace === null) return null; - return { - adapterConfig, - useProjectWorkspace, - }; -} - export function formatRuntimeWorkspaceWarningLog(warning: string) { return { stream: "stdout" as const, @@ -2092,6 +2072,15 @@ export function heartbeatService( const taskKey = deriveTaskKeyWithHeartbeatFallback(context, null); const sessionCodec = getAdapterSessionCodec(agent.adapterType); const issueId = readNonEmptyString(context.issueId); + if (issueId && canApplyTaskWorkspaceSelectionAtAdmission({ admittedInput: parseObject(run.runnerProfileJson).nativeExecutionInput, restarting: Boolean(runOptions.nativeRestartRecovery), hasLeaseOwner: Boolean(runOptions.nativeLeaseOwner) })) { + const changed = await executionWorkspacesSvc.applyPendingTaskWorkspaceSelection({ companyId: agent.companyId, + issueId, runId: run.id, actor: { type: "agent", agentId: agent.id, companyId: agent.companyId, source: "agent_key" } }); + if (changed) { + delete context.resumeSessionParams; + delete context.resumeSessionDisplayId; + context.workspaceSelectionChanged = true; + } + } let issueContext = issueId ? await getIssueExecutionContext(agent.companyId, issueId) : null; @@ -2243,21 +2232,28 @@ export function heartbeatService( const defaultIsolatedWorkspacesEnabled = isolatedWorkspacesEnabled && experimentalInstanceSettings.enableIsolatedWorkspacesByDefault; + const [taskWorkspaceIntentRow] = issueId ? await db.select({ intent: issues.workspaceSelection }).from(issues) + .where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId))) : []; + const runtimeWorkspaceSelectionEnabled = taskWorkspaceRuntimeSelectionEnabled({ legacyUiEnabled: isolatedWorkspacesEnabled, hasTypedSelection: Boolean(taskWorkspaceIntentRow?.intent), hasBinding: Boolean(issueContext?.executionWorkspaceId) }); const parsedIssueExecutionWorkspaceSettings = parseIssueExecutionWorkspaceSettings( issueContext?.executionWorkspaceSettings, ); - const issueExecutionWorkspaceSettings = isolatedWorkspacesEnabled + const issueExecutionWorkspaceSettings = runtimeWorkspaceSelectionEnabled ? parsedIssueExecutionWorkspaceSettings : null; const environmentExecutionWorkspaceSettings = selectEnvironmentExecutionWorkspaceSettings( parsedIssueExecutionWorkspaceSettings, - isolatedWorkspacesEnabled, + runtimeWorkspaceSelectionEnabled, ); const contextProjectId = readNonEmptyString(context.projectId); - const executionProjectId = issueContext?.projectId ?? contextProjectId; - const projectContext = executionProjectId + const boundSourceWorkspace = issueContext?.executionWorkspaceId + ? await executionWorkspacesSvc.getById(issueContext.executionWorkspaceId) : null; + const [selectedWorkspaceSource] = !boundSourceWorkspace && issueContext?.projectWorkspaceId + ? await db.select({ projectId: projectWorkspaces.projectId }).from(projectWorkspaces) + .where(and(eq(projectWorkspaces.id, issueContext.projectWorkspaceId), eq(projectWorkspaces.companyId, agent.companyId))) : []; + const loadProjectContext = async (projectId: string | null | undefined) => projectId ? await db .select({ id: projects.id, @@ -2268,6 +2264,8 @@ export function heartbeatService( .where(and( eq(projectWorkspaces.projectId, projects.id), eq(projectWorkspaces.companyId, agent.companyId), + sql`(nullif(btrim(${projectWorkspaces.cwd}), '') is not null + or nullif(btrim(${projectWorkspaces.repoUrl}), '') is not null)`, )), ).mapWith(Boolean), env: projects.env, @@ -2276,12 +2274,23 @@ export function heartbeatService( .from(projects) .where( and( - eq(projects.id, executionProjectId), + eq(projects.id, projectId), eq(projects.companyId, agent.companyId), ), ) .then((rows) => rows[0] ?? null) : null; + const taskProjectId = issueContext ? issueContext.projectId : contextProjectId; + const taskProjectContext = await loadProjectContext(taskProjectId); + const executionProjectId = boundSourceWorkspace ? boundSourceWorkspace.projectId + : taskWorkspaceIntentRow?.intent?.selection.kind === "task_directory" ? null + : selectedWorkspaceSource?.projectId ?? (taskProjectContext?.hasWorkspace ? taskProjectId : null); + if (issueContext) { + if (taskProjectId) context.projectId = taskProjectId; + else delete context.projectId; + } + const workspaceProjectContext = executionProjectId === taskProjectId + ? taskProjectContext : await loadProjectContext(executionProjectId); const acceptedPlanContinuationWake = issueContext && !isConversation(issueContext) ? readNonEmptyString(context.workspaceRefreshReason) === "accepted_plan_confirmation" || @@ -2390,9 +2399,13 @@ export function heartbeatService( ); issueContext = { ...issueContext, responsibleUserId }; } + const workspacePolicyContext = workspaceProjectContext ?? ( + !boundSourceWorkspace && !taskWorkspaceIntentRow?.intent && !issueContext?.projectWorkspaceId + ? taskProjectContext : null + ); const parsedProjectExecutionWorkspacePolicy = parseProjectExecutionWorkspacePolicy( - projectContext?.executionWorkspacePolicy, + workspacePolicyContext?.executionWorkspacePolicy, ); const projectExecutionWorkspacePolicy = applyDefaultIsolatedExecutionWorkspacePolicy({ @@ -2403,7 +2416,7 @@ export function heartbeatService( defaultIsolatedWorkspacesEnabled, // Projects without workspace configuration get a plain managed // directory. The operator default cannot turn it into a worktree. - hasProjectWorkspace: projectContext?.hasWorkspace ?? false, + hasProjectWorkspace: workspaceProjectContext?.hasWorkspace ?? false, }); const retainedTrust = await resolveAndRetainRunTrustPreset(db, { companyId: agent.companyId, @@ -2413,12 +2426,11 @@ export function heartbeatService( companyId: agent.companyId, permissions: agent.permissions, }, - project: projectContext - ? { - companyId: agent.companyId, - // Workspace feature gates must not erase authorization policy. - executionWorkspacePolicy: projectContext.executionWorkspacePolicy, - } + project: taskProjectContext + ? { companyId: agent.companyId, executionWorkspacePolicy: taskProjectContext.executionWorkspacePolicy } + : null, + workspaceSourceProject: workspaceProjectContext && workspaceProjectContext.id !== taskProjectContext?.id + ? { companyId: agent.companyId, executionWorkspacePolicy: workspaceProjectContext.executionWorkspacePolicy } : null, issue: issueContext ? { @@ -2510,7 +2522,7 @@ export function heartbeatService( delete context.paperclipContinuationSummary; } const taskSessionDecodedParams = normalizeSessionParams( - sessionCodec.deserialize(taskSession?.sessionParamsJson ?? null), + sessionCodec.deserialize(context.workspaceSelectionChanged ? null : taskSession?.sessionParamsJson ?? null), ); const explicitResumeSessionParams = normalizeResumeParamsForAdapter( agent.adapterType, @@ -2567,6 +2579,18 @@ export function heartbeatService( policy: { trustPreset, experimentalInstanceSettings }, services: { issuesSvc, getPinnedSkillTestContext }, }); + const sourceWorkspaceConfig = buildExecutionWorkspaceAdapterConfig({ + agentConfig: config, + projectPolicy: projectExecutionWorkspacePolicy, + issueSettings: issueExecutionWorkspaceSettings, + mode: requestedExecutionWorkspaceMode, + legacyUseProjectWorkspace: + issueAssigneeOverrides?.useProjectWorkspace ?? null, + adapterConfigOverrides: { + ...Object.fromEntries(Object.entries(issueAssigneeOverrides?.adapterConfig ?? {}).filter(([key]) => requestedAiBinding?.mode !== "router" || !["provider", "acpxAgent", "model", "modelReasoningEffort", "reasoningEffort", "effort", "variant"].includes(key))), + ...(requestedAiBinding?.mode === "router" ? parseObject(parseObject(context.aiRouterSelection).runtimeConfig) : {}), + }, + }); const { persistedRunnerProfile, persistedNativeExecutionInput, @@ -2585,23 +2609,27 @@ export function heartbeatService( selectedEnvironmentId, selectedEnvironmentForConfig, nativeChatWorkspaceScope, - nativeChatExpectedCwd, + nativeChatExpectedCwd: selectedNativeChatExpectedCwd, + materializeNativeChatWorktree, + explicitTaskDirectory, useIsolatedTaskDirectory, } = await selectHeartbeatEnvironment(db, { run, agent, - task: { issueRef, issueContext, issueId, context, executionProjectId, projectContext }, + task: { issueRef, issueContext, issueId, context, executionProjectId, workspaceProjectContext, boundSourceWorkspace, + hasSelectedWorkspaceSource: Boolean(selectedWorkspaceSource), taskWorkspaceIntent: taskWorkspaceIntentRow?.intent ?? null, responsibleUserId }, policy: { - trustPreset, requestedExecutionWorkspaceMode, isolatedWorkspacesEnabled, + trustPreset, requestedExecutionWorkspaceMode, isolatedWorkspacesEnabled: runtimeWorkspaceSelectionEnabled, projectExecutionWorkspacePolicy, issueExecutionWorkspaceSettings, }, - config: { config, issueAssigneeOverrides }, + config: { config, sourceWorkspaceConfig, issueAssigneeOverrides }, services: { executionWorkspacesSvc, environmentsSvc, instanceSettings, findSharedWorkspaceHolder }, }); + let nativeChatExpectedCwd = selectedNativeChatExpectedCwd; const executionConfiguration = await prepareHeartbeatExecutionConfiguration(db, { run, agent, - task: { issueId, context, responsibleUserId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision, projectContext, routineEnvContext }, - config: { config, issueAssigneeOverrides, requestedAiBinding, aiBinding }, - policy: { trustPreset, projectExecutionWorkspacePolicy, issueExecutionWorkspaceSettings, requestedExecutionWorkspaceMode, useIsolatedTaskDirectory }, + task: { issueId, context, responsibleUserId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision, taskProjectContext, routineEnvContext }, + config: { sourceWorkspaceConfig, aiBinding }, + policy: { trustPreset, useIsolatedTaskDirectory, explicitTaskDirectory }, environment: { selectedEnvironmentId, selectedEnvironmentForConfig, resolvedInstanceSettings, persistedNativeExecutionInput }, services: { secretsSvc, companySkills, appendRunEvent, finalizeAiConnectionBusyDeferral }, onFailureSecretsResolved: (readSecrets) => { readFailureReportSecrets = readSecrets; }, @@ -2623,7 +2651,7 @@ export function heartbeatService( ]; const preparedSession = await prepareHeartbeatSession(db, { run, agent, - task: { context, issueRef, issueContext, projectContext, wakeCommentId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision }, + task: { context, issueRef, issueContext, taskProjectContext, workspaceProjectContext, wakeCommentId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision }, session: { taskSession, taskSessionDecodedParams, explicitResumeSessionParams, explicitResumeSessionDisplayId, sessionCodec }, config: { runtimeConfig, agentIdentity, issueAssigneeOverrides, originalAiIssueOverrides, @@ -2640,6 +2668,7 @@ export function heartbeatService( const { resolvedWorkspace, hostExecutionWorkspaceConfig, + skipHostProvision, latestWorkspaceConfigMetadata, workspaceConfigFreshness, workspaceOperationRecorder, @@ -2662,13 +2691,13 @@ export function heartbeatService( issueRef, issueId, context, - executionProjectId, responsibleUserId, previousSessionParams, + workspaceSelection: taskWorkspaceIntentRow?.intent ?? null, }, policy: { trustPreset, - isolatedWorkspacesEnabled, + isolatedWorkspacesEnabled: runtimeWorkspaceSelectionEnabled, effectiveExecutionWorkspaceMode, requestedExecutionWorkspaceMode, useIsolatedTaskDirectory, @@ -2704,7 +2733,6 @@ export function heartbeatService( envOrchestrator, executionWorkspacesSvc, workspaceOperationsSvc, - issuesSvc, resolveWorkspaceForRun, resolveReusedGitWorkspaceAnchor, appendRunEvent, @@ -2721,6 +2749,9 @@ export function heartbeatService( catch { throw new ConfigurationIncompleteFailure("Project authentication conflicts with this agent’s managed AI connection", { configurationIncomplete: { reason: "ai_connection_incompatible", actionUrl: `/agents/${agent.id}/runtime` } }); } } const remoteExecution = realizationResult.remoteExecution; + if (nativeChatWorkspaceScope && materializeNativeChatWorktree) { + nativeChatExpectedCwd = nativeChatWorkspaceCwd(nativeChatWorkspaceScope, persistedExecutionWorkspace, true); + } if ( nativeChatWorkspaceScope && (executionTarget?.kind === "remote" || @@ -3029,7 +3060,7 @@ export function heartbeatService( } prepareHeartbeatRuntimeServiceIntents({ hostExecutionWorkspaceConfig, context, trustPreset }); if ( - executionWorkspace.projectId && + !issueContext && executionWorkspace.projectId && !readNonEmptyString(context.projectId) ) { context.projectId = executionWorkspace.projectId; @@ -3173,7 +3204,7 @@ export function heartbeatService( ); const runtimeServices = await startHeartbeatRuntimeServices(db, { run, agent, issueId, issueRef, executionWorkspace, context, issuesSvc, onLog, - persistedExecutionWorkspace, hostExecutionWorkspaceConfig, adapterEnv, workspaceOperationRecorder, + persistedExecutionWorkspace, hostExecutionWorkspaceConfig, skipHostProvision, adapterEnv, workspaceOperationRecorder, }); const adapter = getServerAdapter(agent.adapterType); const durableGoalControlRun = @@ -3251,6 +3282,16 @@ export function heartbeatService( activeEnvironmentLease, isDotRun, projectRepositoryPaths, + onCheckpoint: async (metrics) => { + await appendRunEvent(run, { eventType: "lifecycle", stream: "system", level: "info", + message: "workspace_checkpoint", payload: { phase: "workspace_checkpoint", ...metrics } }); + }, + onPhase: async (name, work) => { + const started = Date.now(); + try { return await work(); } + finally { await appendRunEvent(run, { eventType: "lifecycle", stream: "system", level: "info", + message: name, payload: { phase: name, durationMs: Date.now() - started } }); } + }, }, config: { runtimeConfig, @@ -3793,6 +3834,12 @@ export function heartbeatService( eq(heartbeatRuns.controllerBootId, legacyControllerBootId), inArray(heartbeatRuns.status, ["succeeded", "failed", "cancelled", "timed_out", "interrupted"]))); + latestRun = await getRun(run.id).catch(() => null); + if (latestRun && isHeartbeatRunTerminalStatus(latestRun.status) && !isNativeRunnerOwnershipHeld(latestRun)) { + await appendRunEvent(run, { eventType: "lifecycle", stream: "system", level: "info", message: "task.run.measured", + payload: { phase: "task.run.measured", durationMs: Date.now() - (latestRun.startedAt ?? latestRun.createdAt).getTime(), + outcome: latestRun.status, durableSettlement: true } }).catch(error => logger.warn({ runId: run.id, error }, "Could not record terminal run duration")); + } } finally { controllerLease.stop(); activeRunExecutions.delete(run.id); diff --git a/server/src/services/heartbeat/environment-selection.test.ts b/server/src/services/heartbeat/environment-selection.test.ts index c55395c3d7a..5403f8164ef 100644 --- a/server/src/services/heartbeat/environment-selection.test.ts +++ b/server/src/services/heartbeat/environment-selection.test.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; import { mkdtemp, mkdir, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agents, companies, createDb, environments, heartbeatRuns, issues, projects, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, type Db } from "@paperclipai/db"; +import { agents, companies, createDb, environments, heartbeatRuns, issues, projects, projectWorkspaces, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, type Db } from "@paperclipai/db"; import { eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js"; @@ -46,13 +46,13 @@ describe.skipIf(!support.supported)("heartbeat environment selection boundary", const [run] = await db.insert(heartbeatRuns).values({ companyId: company.id, agentId: agent.id, issueId: issue.id, scopeKind: "issue", status: "running", runtimeMode: "legacy" }).returning(); const input: HeartbeatEnvironmentSelectionInput = { run, agent, - task: { issueRef: issue, issueContext: issue, issueId: issue.id, executionProjectId: project.id, projectContext: { hasWorkspace: false }, context: { issueId: issue.id } }, + task: { issueRef: issue, issueContext: issue, issueId: issue.id, executionProjectId: project.id, workspaceProjectContext: { hasWorkspace: false }, boundSourceWorkspace: null, hasSelectedWorkspaceSource: false, taskWorkspaceIntent: null, responsibleUserId: null, context: { issueId: issue.id } }, policy: { trustPreset: { kind: "standard", preset: "standard", boundary: null, sourcePresets: {} }, requestedExecutionWorkspaceMode: "shared_workspace", isolatedWorkspacesEnabled: true, projectExecutionWorkspacePolicy: null, issueExecutionWorkspaceSettings: null, }, - config: { config: {}, issueAssigneeOverrides: null }, + config: { config: {}, sourceWorkspaceConfig: {}, issueAssigneeOverrides: null }, services: { environmentsSvc: environmentService(db), executionWorkspacesSvc: executionWorkspaceService(db), instanceSettings: instanceSettingsService(db), findSharedWorkspaceHolder: vi.fn(async () => null) }, }; const workspace = async () => { @@ -126,17 +126,33 @@ describe.skipIf(!support.supported)("heartbeat environment selection boundary", await db.update(issues).set({ originKind: "chat_channel", projectId: null }).where(eq(issues.id, issue.id)); input.agent.adapterType = "paperclip_runner"; input.task.executionProjectId = null; - input.task.projectContext = null; + input.task.workspaceProjectContext = null; const result = await selectHeartbeatEnvironment(db, input); expect(result.nativeChatWorkspaceScope).toMatchObject({ companyId: input.agent.companyId, agentId: input.agent.id, issueId: issue.id, projectId: null }); expect(result.nativeChatExpectedCwd).toBe(result.nativeChatWorkspaceScope?.taskRoot); - expect(result.nativeChatExpectedCwd).toContain(`chat-workspaces/${input.agent.companyId}/${input.agent.id}/${issue.id}`); + expect(result.nativeChatExpectedCwd).toContain(`isolated-workspaces/${input.agent.companyId}/${issue.id}`); }); - it("rejects a project chat without a task-owned isolated workspace", async () => { - const { input, issue } = await fixture(); - await db.update(issues).set({ originKind: "chat_channel" }).where(eq(issues.id, issue.id)); + it.each(["git_worktree", "project_primary"] as const)("only prepares fresh native chat isolation with a %s source strategy", async (strategy) => { + const { input, issue, project } = await fixture(); + const [source] = await db.insert(projectWorkspaces).values({ companyId: input.agent.companyId, projectId: project.id, + name: "Chat source", cwd: home, sourceType: "local_path", isPrimary: true }).returning(); + const intent = { version: 1 as const, source: "explicit" as const, selection: { kind: "configured_source" as const, projectWorkspaceId: source.id, mode: "managed_isolated" as const } }; + await db.update(issues).set({ originKind: "chat_channel", projectWorkspaceId: source.id, workspaceSelection: intent }).where(eq(issues.id, issue.id)); + input.task.issueRef!.projectWorkspaceId = source.id; + input.task.workspaceProjectContext = { hasWorkspace: true }; + input.task.hasSelectedWorkspaceSource = true; + input.task.taskWorkspaceIntent = intent; input.agent.adapterType = "paperclip_runner"; - await expect(selectHeartbeatEnvironment(db, input)).rejects.toThrow("External chat requires a task-owned isolated workspace"); + input.policy.requestedExecutionWorkspaceMode = "isolated_workspace"; + input.config.sourceWorkspaceConfig = { workspaceStrategy: { type: strategy } }; + if (strategy === "project_primary") { + await expect(selectHeartbeatEnvironment(db, input)).rejects.toThrow("External chat requires a task-owned isolated workspace"); + } else { + const result = await selectHeartbeatEnvironment(db, input); + expect(result).toMatchObject({ materializeNativeChatWorktree: true, nativeChatExpectedCwd: null, existingExecutionWorkspace: null }); + expect(result.nativeChatWorkspaceScope?.projectId).toBe(project.id); + expect((await db.select().from(issues).where(eq(issues.id, issue.id)))[0].executionWorkspaceId).toBeNull(); + } }); }); diff --git a/server/src/services/heartbeat/environment-selection.ts b/server/src/services/heartbeat/environment-selection.ts index 78509cdac29..6af79911b67 100644 --- a/server/src/services/heartbeat/environment-selection.ts +++ b/server/src/services/heartbeat/environment-selection.ts @@ -1,5 +1,6 @@ -import type { Db, agents, heartbeatRuns, issues } from "@paperclipai/db"; -import type { IssueExecutionWorkspaceSettings, ProjectExecutionWorkspacePolicy } from "@paperclipai/shared"; +import { heartbeatRuns, issues, type Db, type agents } from "@paperclipai/db"; +import { and, eq, ne, sql } from "drizzle-orm"; +import type { ExecutionWorkspace, IssueExecutionWorkspaceSettings, ProjectExecutionWorkspacePolicy } from "@paperclipai/shared"; import { parseObject } from "../../adapters/utils.js"; import { logger } from "../../middleware/logger.js"; import { parseNativeExecutionInput } from "../../vendor/paperclip-runner/index.js"; @@ -7,11 +8,13 @@ import { isExecutionForcedToKubernetes } from "../execution-allowlist.js"; import { parseExecutionPolicyBootstrapEnv } from "../execution-policy-bootstrap.js"; import { parseIssueExecutionWorkspaceSettings, + resolveEffectiveWorkspaceStrategyType, resolveExecutionWorkspaceEnvironmentId, resolveExecutionWorkspaceMode, resolveSharedWorkspaceConcurrency, type ParsedExecutionWorkspaceMode, } from "../execution-workspace-policy.js"; +import { assertTaskWorkspaceAccess, assertTaskWorkspaceSourceProjectAccess } from "../task-workspace-source-access.js"; import { shouldUseIsolatedTaskDirectory } from "../isolated-task-directory.js"; import { findNativeChatWorkspaceScope, @@ -42,7 +45,11 @@ export interface HeartbeatEnvironmentSelectionInput { issueId: string | null; context: Record; executionProjectId: string | null; - projectContext: { hasWorkspace: boolean } | null; + workspaceProjectContext: { hasWorkspace: boolean } | null; + boundSourceWorkspace: ExecutionWorkspace | null; + hasSelectedWorkspaceSource: boolean; + taskWorkspaceIntent: Issue["workspaceSelection"]; + responsibleUserId: string | null; }; policy: { trustPreset: TrustPresetResolution; @@ -53,10 +60,11 @@ export interface HeartbeatEnvironmentSelectionInput { }; config: { config: Record; + sourceWorkspaceConfig: Record; issueAssigneeOverrides: { adapterConfig: Record | null } | null; }; services: { - executionWorkspacesSvc: Pick, "getById">; + executionWorkspacesSvc: Pick, "getById" | "validateSelection">; environmentsSvc: Pick, "ensureLocalEnvironment" | "findManagedSandboxEnvironment" | "findKubernetesEnvironment" | "ensureKubernetesEnvironment" | "getById">; instanceSettings: Pick, "get" | "getExperimental">; @@ -70,12 +78,12 @@ function readNonEmptyString(value: unknown): string | null { export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnvironmentSelectionInput) { const { run, agent } = input; - const { issueRef, issueContext, issueId, context, executionProjectId, projectContext } = input.task; + const { issueRef, issueContext, issueId, context, executionProjectId, workspaceProjectContext, boundSourceWorkspace, hasSelectedWorkspaceSource, taskWorkspaceIntent, responsibleUserId } = input.task; const { - trustPreset, requestedExecutionWorkspaceMode, isolatedWorkspacesEnabled, + trustPreset, requestedExecutionWorkspaceMode, isolatedWorkspacesEnabled: runtimeWorkspaceSelectionEnabled, projectExecutionWorkspacePolicy, issueExecutionWorkspaceSettings, } = input.policy; - const { config, issueAssigneeOverrides } = input.config; + const { config, sourceWorkspaceConfig, issueAssigneeOverrides } = input.config; const { executionWorkspacesSvc, environmentsSvc, instanceSettings, findSharedWorkspaceHolder } = input.services; // A native run's execution input is immutable once persisted. Recovery must therefore // restore the workspace bound to that input rather than consulting the issue's current @@ -119,6 +127,16 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron workspaceReuseRequest.existingExecutionWorkspaceAvailable ? existingExecutionWorkspace : null; + const workspaceAuthorizationActor = { type: "agent" as const, agentId: agent.id, companyId: agent.companyId, + source: "agent_jwt" as const, runId: run.id, onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId }; + if (!persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId) { + const workspaceRequiringAccess = reusableExistingExecutionWorkspace ?? boundSourceWorkspace; + if (workspaceRequiringAccess) { + await assertTaskWorkspaceAccess(db, workspaceAuthorizationActor, agent.companyId, workspaceRequiringAccess.id, { write: true, issueId }); + } else if (!isDotRun && executionProjectId && (hasSelectedWorkspaceSource || workspaceProjectContext?.hasWorkspace)) { + await assertTaskWorkspaceSourceProjectAccess(db, workspaceAuthorizationActor, agent.companyId, executionProjectId, { write: resolveEffectiveWorkspaceStrategyType(requestedExecutionWorkspaceMode, sourceWorkspaceConfig) !== "git_worktree", issueId }); + } + } const requestedReusableExecutionWorkspaceConfig = reusableExistingExecutionWorkspace?.config ?? null; const localEnvironment = await environmentsSvc.ensureLocalEnvironment( @@ -241,8 +259,10 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron companyId: agent.companyId, agentId: agent.id, issueId, + admittedCwd: persistedNativeExecutionInput && persistedNativeExecutionInput.schema !== "paperclip.native-execution-input.v6" + ? persistedNativeExecutionInput.workspace.cwd : null, }); - const nativeChatExpectedCwd = nativeChatWorkspaceScope + let nativeChatExpectedCwd = nativeChatWorkspaceScope ? nativeChatWorkspaceCwd( nativeChatWorkspaceScope, reusableExistingExecutionWorkspace, @@ -265,13 +285,20 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron "native_chat_workspace_scope_mismatch", ); } + const materializeNativeChatWorktree = Boolean( + nativeChatWorkspaceScope?.projectId && !persistedNativeExecutionInput && !existingExecutionWorkspace && + taskWorkspaceIntent?.selection.kind === "configured_source" && + taskWorkspaceIntent.selection.mode === "managed_isolated" && + requestedExecutionWorkspaceMode === "isolated_workspace" && + resolveEffectiveWorkspaceStrategyType(requestedExecutionWorkspaceMode, sourceWorkspaceConfig) === "git_worktree", + ); if ( nativeChatWorkspaceScope && - (!nativeChatExpectedCwd || + ((!nativeChatExpectedCwd && !materializeNativeChatWorktree) || executionProjectId !== nativeChatWorkspaceScope.projectId) ) { throw new ConfigurationIncompleteFailure( - "External chat requires a task-owned isolated workspace. Configure and select an existing isolated worktree for this project task; shared project workspaces cannot be used for external chat.", + "External chat requires a task-owned isolated workspace. Select a managed isolated source or an existing task-owned isolated worktree; shared project workspaces cannot be used for external chat.", { configurationIncomplete: { reason: "native_chat_workspace_isolation_required", @@ -280,6 +307,19 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron }, ); } + if (!nativeRecoveryExecutionWorkspaceId && reusableExistingExecutionWorkspace && + effectiveExecutionWorkspaceMode === "shared_workspace" && + selectedEnvironmentForConfig?.driver !== "local" && selectedEnvironmentForConfig?.driver !== "ssh") { + const [remoteSharedHolder] = await db.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, agent.companyId), ne(heartbeatRuns.id, run.id), eq(heartbeatRuns.status, "running"), + sql`exists (select 1 from ${issues} where ${issues.companyId} = ${agent.companyId} + and ${issues.executionWorkspaceId} = ${reusableExistingExecutionWorkspace.id} + and (${issues.executionRunId} = ${heartbeatRuns.id} or ${issues.id}::text = coalesce(${heartbeatRuns.contextSnapshot}->>'issueId', ${heartbeatRuns.contextSnapshot}->>'taskId')))`, + )).limit(1); + if (remoteSharedHolder) throw new ConfigurationIncompleteFailure("This environment cannot place concurrent agents in one physical shared folder. Use local/SSH sharing or isolated task workspaces.", { + configurationIncomplete: { reason: "shared_realization_unsupported", issueId }, + }); + } const sharedWorkspaceConcurrency = resolveSharedWorkspaceConcurrency({ projectPolicy: projectExecutionWorkspacePolicy, issueSettings: issueExecutionWorkspaceSettings, @@ -298,11 +338,17 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron projectWorkspaceId: issueRef.projectWorkspaceId, excludeIssueId: issueRef.id, excludeRunId: run.id, - honorIsolatedWorkspaceModes: isolatedWorkspacesEnabled, + honorIsolatedWorkspaceModes: runtimeWorkspaceSelectionEnabled, }); if (workspaceHolder) { const environmentDriver = selectedEnvironmentForConfig?.driver ?? null; + if (!nativeRecoveryExecutionWorkspaceId && sharedWorkspaceConcurrency === "allow" && + (executionForcedToKubernetes || (environmentDriver !== "local" && environmentDriver !== "ssh"))) { + throw new ConfigurationIncompleteFailure("This environment cannot place concurrent agents in one physical shared folder. Use local/SSH sharing or isolated task workspaces.", { + configurationIncomplete: { reason: "shared_realization_unsupported", issueId }, + }); + } const shouldSerialize = sharedWorkspaceConcurrency !== "allow" && (executionForcedToKubernetes || @@ -365,11 +411,17 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron ); } } + if (taskWorkspaceIntent && !boundSourceWorkspace && !nativeRecoveryExecutionWorkspaceId) { + await executionWorkspacesSvc.validateSelection({ companyId: agent.companyId, + actor: { type: "agent", agentId: agent.id, companyId: agent.companyId, runId: run.id, source: "agent_jwt", onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId }, + selection: taskWorkspaceIntent.selection, issueId }); + } + const explicitTaskDirectory = taskWorkspaceIntent?.selection.kind === "task_directory"; const useIsolatedTaskDirectory = issueRef !== null && shouldUseIsolatedTaskDirectory({ trustPreset: trustPreset.kind, environmentDriver: selectedEnvironmentForConfig?.driver ?? null, mode: requestedExecutionWorkspaceMode, - hasProjectWorkspace: projectContext?.hasWorkspace ?? false, + hasProjectWorkspace: workspaceProjectContext?.hasWorkspace ?? false, projectWorkspaceId: issueRef.projectWorkspaceId, workspaceStrategies: [ config.workspaceStrategy, @@ -397,6 +449,8 @@ export async function selectHeartbeatEnvironment(db: Db, input: HeartbeatEnviron selectedEnvironmentForConfig, nativeChatWorkspaceScope, nativeChatExpectedCwd, + materializeNativeChatWorktree, + explicitTaskDirectory, useIsolatedTaskDirectory, }; } diff --git a/server/src/services/heartbeat/execution-configuration.ts b/server/src/services/heartbeat/execution-configuration.ts index 859371de9aa..99bff95999b 100644 --- a/server/src/services/heartbeat/execution-configuration.ts +++ b/server/src/services/heartbeat/execution-configuration.ts @@ -1,6 +1,6 @@ import { eq, sql } from "drizzle-orm"; import { heartbeatRuns, type Db, type agents, type projects } from "@paperclipai/db"; -import type { AiConnectionBinding, AiRuntimeConnectionBinding } from "@paperclipai/shared"; +import type { AiConnectionBinding } from "@paperclipai/shared"; import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils"; import { parseObject } from "../../adapters/utils.js"; import { logger } from "../../middleware/logger.js"; @@ -14,7 +14,6 @@ import type { companySkillService } from "../company-skills.js"; import { connectionIntentService } from "../connection-intents.js"; import { prepareConnectionInstructionDelivery } from "../connection-instructions.js"; import { applyConnectorSkills, prepareConnectorSkillDelivery, resolveConnectorAssignments } from "../connector-runtime.js"; -import { buildExecutionWorkspaceAdapterConfig } from "../execution-workspace-policy.js"; import { resolveManagedGitHubIdentitySelection } from "../git-credentials.js"; import { resolveAssignedConnectionInstructionsForRun } from "../native-runtime/assigned-mcp-tools.js"; import { recordFailedSkillPreparation, type NativeRunHistoricalSpan } from "../native-runtime/native-run-trace.js"; @@ -45,17 +44,15 @@ export interface HeartbeatExecutionConfigurationInput { responsibleUserId: string | null; acceptedPlanContinuationWake: boolean; acceptedPlanWakeRoutingDecision: Awaited> | null; - projectContext: Pick | null; + taskProjectContext: Pick | null; routineEnvContext: Awaited["getRoutineEnvForExecutionIssue"]>>; }; config: { - config: Record; - issueAssigneeOverrides: { adapterConfig: Record | null; useProjectWorkspace: boolean | null } | null; - requestedAiBinding: AiRuntimeConnectionBinding | undefined; + sourceWorkspaceConfig: Record; aiBinding: AiConnectionBinding | undefined; }; policy: Pick; + "trustPreset" | "useIsolatedTaskDirectory"> & { explicitTaskDirectory: boolean }; environment: Pick; services: { @@ -73,27 +70,17 @@ export interface HeartbeatExecutionConfigurationInput { /** Assembles effective run configuration; the executor retains cleanup ownership. */ export async function prepareHeartbeatExecutionConfiguration(db: Db, input: HeartbeatExecutionConfigurationInput) { const { run, agent, onFailureSecretsResolved, onManagedAiRuntimeAcquired, onIdentityRedactorPrepared } = input; - const { issueId, context, responsibleUserId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision, projectContext, routineEnvContext } = input.task; - const { config, issueAssigneeOverrides, requestedAiBinding, aiBinding } = input.config; - const { trustPreset, projectExecutionWorkspacePolicy, issueExecutionWorkspaceSettings, requestedExecutionWorkspaceMode, useIsolatedTaskDirectory } = input.policy; + const { issueId, context, responsibleUserId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision, taskProjectContext, routineEnvContext } = input.task; + const { sourceWorkspaceConfig, aiBinding } = input.config; + const { trustPreset, useIsolatedTaskDirectory, explicitTaskDirectory } = input.policy; const { selectedEnvironmentId, selectedEnvironmentForConfig, resolvedInstanceSettings, persistedNativeExecutionInput } = input.environment; const { secretsSvc, companySkills, appendRunEvent, finalizeAiConnectionBusyDeferral } = input.services; let managedAiRuntime: ManagedAiRuntime | undefined; - const workspaceManagedConfig = buildExecutionWorkspaceAdapterConfig({ - agentConfig: config, - projectPolicy: projectExecutionWorkspacePolicy, - issueSettings: issueExecutionWorkspaceSettings, - mode: requestedExecutionWorkspaceMode, - legacyUseProjectWorkspace: - issueAssigneeOverrides?.useProjectWorkspace ?? null, - }); const mergedConfig = { - ...workspaceManagedConfig, - ...Object.fromEntries(Object.entries(issueAssigneeOverrides?.adapterConfig ?? {}).filter(([key]) => requestedAiBinding?.mode !== "router" || !["provider", "acpxAgent", "model", "modelReasoningEffort", "reasoningEffort", "effort", "variant"].includes(key))), - ...(requestedAiBinding?.mode === "router" ? parseObject(parseObject(context.aiRouterSelection).runtimeConfig) : {}), + ...sourceWorkspaceConfig, // The base below is already task-owned. Keep directory transport while // preserving isolated mode and the mandatory sandbox preflight. - ...(useIsolatedTaskDirectory ? { workspaceStrategy: { type: "project_primary" } } : {}), + ...((useIsolatedTaskDirectory || explicitTaskDirectory) ? { workspaceStrategy: { type: "project_primary" } } : {}), }; const configSnapshot = buildExecutionWorkspaceConfigSnapshot( mergedConfig, @@ -141,11 +128,11 @@ export async function prepareHeartbeatExecutionConfiguration(db: Db, input: Hear environmentId: selectedEnvironmentForConfig?.id ?? null, environmentEnv: aiBinding ? stripAiAuthBindings(selectedEnvironmentForConfig?.envVars) : selectedEnvironmentForConfig?.envVars ?? null, environmentDriver: selectedEnvironmentForConfig?.driver ?? null, - projectId: projectContext?.id ?? null, + projectId: taskProjectContext?.id ?? null, routineId: routineEnvContext.routineId, responsibleUserId, executionRunConfig: aiBinding ? { ...executionRunConfig, env: stripAiAuthBindings(executionRunConfig.env) } : executionRunConfig, - projectEnv: aiBinding ? stripAiAuthBindings(projectContext?.env) : projectContext?.env ?? null, + projectEnv: aiBinding ? stripAiAuthBindings(taskProjectContext?.env) : taskProjectContext?.env ?? null, routineEnv: aiBinding ? stripAiAuthBindings(routineEnvContext.env) : routineEnvContext.env, secretsSvc, trustPreset, diff --git a/server/src/services/heartbeat/runtime-execution.test.ts b/server/src/services/heartbeat/runtime-execution.test.ts index 6fdf8fa0e9b..60efde0bb50 100644 --- a/server/src/services/heartbeat/runtime-execution.test.ts +++ b/server/src/services/heartbeat/runtime-execution.test.ts @@ -209,6 +209,28 @@ describe.skipIf(!support.supported)("heartbeat runtime execution boundary", () = expectCleanup(input); }); + it("records native restoration inside the workspace finalization operation", async () => { + const { input, run } = await fixture(true); + const restore = vi.fn(async () => { + expect(await finalizations()).toMatchObject([{ + status: "running", metadata: { owningService: "native_workspace_finalizer" }, + }]); + }); + input.workspace.nativeWorkspaceSync = { + mode: "host_current", + reference: { schema: "paperclip.native-workspace-sync/v3", state: "prepared", + descriptorSha256: "a".repeat(64), baselineSha256: "b".repeat(64), finalHostSha256: null, + workspaceId: run.id, leaseId: randomUUID(), providerLeaseId: "sandbox", remoteCwd: "/workspace", resourceDisposition: null }, + restoreWorkspace: restore, + cleanup: vi.fn(async () => {}), + }; + expect(await executeHeartbeatRuntime(db, input)).toMatchObject({ dispatched: true }); + expect(restore).toHaveBeenCalledOnce(); + expect(await finalizations()).toMatchObject([{ + status: "succeeded", metadata: { owningService: "native_workspace_finalizer" }, + }]); + }); + it("records a failed barrier if the successful finalization write fails", async () => { const { input } = await fixture(); const error = new Error("finalization write failed"); diff --git a/server/src/services/heartbeat/runtime-execution.ts b/server/src/services/heartbeat/runtime-execution.ts index 814ffa2e709..bb5f5b99fe2 100644 --- a/server/src/services/heartbeat/runtime-execution.ts +++ b/server/src/services/heartbeat/runtime-execution.ts @@ -291,192 +291,186 @@ export async function executeHeartbeatRuntime(db: Db, input: HeartbeatRuntimeExe const recordWorkspaceFinalize = async ( status: "succeeded" | "failed", metadata?: Record, + restore?: () => Promise, ) => { if (adapterFinalizeOutcome) return; - let finalizeBranchMetadata: Record | null = null; - let finalizeBranchRepairMetadata: Record | null = - null; - if (status === "succeeded") { - const branchInspection = await inspectFinalizeWorkspaceBranch(); - if (branchInspection) { - let inspection = branchInspection.inspection; - const initialManagedGitWorktreeBranch = - formatManagedGitWorktreeBranchInspection(inspection); - if ( - !inspection.valid && - inspection.reasonCode === "branch_mismatch" && - inspection.repoRoot - ) { - let repairedExpectedBranchName = inspection.expectedBranchName; - try { - const coherence = await ensureGitWorktreeBranchCoherent({ - db, - repoRoot: inspection.repoRoot, - worktreePath: inspection.worktreePath, - expectedBranchName: inspection.expectedBranchName, - actualBranchName: inspection.actualBranchName, - sourceIssue: issueRef - ? { - id: issueRef.id, - identifier: issueRef.identifier, - title: issueRef.title, - workMode: issueRef.workMode, - } - : null, - executionWorkspaceId: branchInspection.workspaceRecord.id, - heartbeatRunId: run.id, - enableWorkspaceBranchReconcileForward: - resolvedInstanceSettings.experimental - .enableWorkspaceBranchReconcileForward, - enableWorkspaceDirtyQuarantineRepair: - resolvedInstanceSettings.experimental - .enableWorkspaceDirtyQuarantineRepair, - persistForwardReconcile: false, - reconcileOperationPhase: "workspace_finalize", - recorder: workspaceOperationRecorder, - }); + await workspaceOperationRecorder.recordOperation({ + phase: "workspace_finalize", cwd: executionWorkspace.cwd, + metadata: { adapterType: agent.adapterType, executionTargetKind: executionTarget?.kind ?? "local", + ...(restore ? { owningService: "native_workspace_finalizer" } : {}), ...metadata }, + run: async () => { + await restore?.(); + let finalizeBranchMetadata: Record | null = null; + let finalizeBranchRepairMetadata: Record | null = + null; + if (status === "succeeded") { + const branchInspection = await inspectFinalizeWorkspaceBranch(); + if (branchInspection) { + let inspection = branchInspection.inspection; + const initialManagedGitWorktreeBranch = + formatManagedGitWorktreeBranchInspection(inspection); if ( - coherence.branchName && - coherence.branchName !== - branchInspection.workspaceRecord.branchName + !inspection.valid && + inspection.reasonCode === "branch_mismatch" && + inspection.repoRoot ) { - repairedExpectedBranchName = coherence.branchName; - executionWorkspace.branchName = coherence.branchName; - executionWorkspace.warnings.push(...coherence.warnings); - } - } catch (repairErr) { - const workspaceValidationFailure = - isWorkspaceValidationFailure(repairErr) ? repairErr : null; - finalizeBranchMetadata = { - executionWorkspaceId: branchInspection.workspaceRecord.id, - ...initialManagedGitWorktreeBranch, - }; - finalizeBranchRepairMetadata = { - attempted: true, - succeeded: false, - initial: initialManagedGitWorktreeBranch, - reason: - repairErr instanceof Error - ? repairErr.message - : String(repairErr), - }; - await workspaceOperationRecorder.recordOperation({ - phase: "workspace_finalize", - cwd: executionWorkspace.cwd, - metadata: { - adapterType: agent.adapterType, - executionTargetKind: executionTarget?.kind ?? "local", - ...metadata, - managedGitWorktreeBranch: finalizeBranchMetadata, - managedGitWorktreeBranchRepair: - finalizeBranchRepairMetadata, - ...(workspaceValidationFailure?.resultJson - ? { - workspaceValidation: - workspaceValidationFailure.resultJson - .workspaceValidation ?? - workspaceValidationFailure.resultJson, - } - : {}), - }, - run: async () => ({ - status: "failed", - stderr: `Managed git worktree branch check failed: ${repairErr instanceof Error ? repairErr.message : String(repairErr)}\n`, - }), - }); - adapterFinalizeOutcome = "failed"; - throw repairErr; - } - - const repairedInspection = - await inspectManagedGitWorktreeBranch({ - worktreePath: inspection.worktreePath, - expectedBranchName: repairedExpectedBranchName, - repoRoot: inspection.repoRoot, - }); - finalizeBranchRepairMetadata = { - attempted: true, - succeeded: repairedInspection.valid, - initial: initialManagedGitWorktreeBranch, - repaired: - formatManagedGitWorktreeBranchInspection( - repairedInspection, - ), - }; - inspection = repairedInspection; - } - - const managedGitWorktreeBranch = - formatManagedGitWorktreeBranchInspection(inspection); - finalizeBranchMetadata = { - executionWorkspaceId: branchInspection.workspaceRecord.id, - ...managedGitWorktreeBranch, - }; - if (!inspection.valid) { - const workspaceValidationFingerprint = - fingerprintFinalizeWorkspaceBranchValidation({ - issueId: issueRef?.id ?? null, - executionWorkspaceId: branchInspection.workspaceRecord.id, - inspection: managedGitWorktreeBranch, - }); - await workspaceOperationRecorder.recordOperation({ - phase: "workspace_finalize", - cwd: executionWorkspace.cwd, - metadata: { - adapterType: agent.adapterType, - executionTargetKind: executionTarget?.kind ?? "local", - ...metadata, - managedGitWorktreeBranch: finalizeBranchMetadata, - ...(finalizeBranchRepairMetadata - ? { + let repairedExpectedBranchName = inspection.expectedBranchName; + try { + const coherence = await ensureGitWorktreeBranchCoherent({ + db, + repoRoot: inspection.repoRoot, + worktreePath: inspection.worktreePath, + expectedBranchName: inspection.expectedBranchName, + actualBranchName: inspection.actualBranchName, + sourceIssue: issueRef + ? { + id: issueRef.id, + identifier: issueRef.identifier, + title: issueRef.title, + workMode: issueRef.workMode, + } + : null, + executionWorkspaceId: branchInspection.workspaceRecord.id, + heartbeatRunId: run.id, + enableWorkspaceBranchReconcileForward: + resolvedInstanceSettings.experimental + .enableWorkspaceBranchReconcileForward, + enableWorkspaceDirtyQuarantineRepair: + resolvedInstanceSettings.experimental + .enableWorkspaceDirtyQuarantineRepair, + persistForwardReconcile: false, + reconcileOperationPhase: "workspace_finalize", + recorder: workspaceOperationRecorder, + }); + if ( + coherence.branchName && + coherence.branchName !== + branchInspection.workspaceRecord.branchName + ) { + repairedExpectedBranchName = coherence.branchName; + executionWorkspace.branchName = coherence.branchName; + executionWorkspace.warnings.push(...coherence.warnings); + } + } catch (repairErr) { + const workspaceValidationFailure = + isWorkspaceValidationFailure(repairErr) ? repairErr : null; + finalizeBranchMetadata = { + executionWorkspaceId: branchInspection.workspaceRecord.id, + ...initialManagedGitWorktreeBranch, + }; + finalizeBranchRepairMetadata = { + attempted: true, + succeeded: false, + initial: initialManagedGitWorktreeBranch, + reason: + repairErr instanceof Error + ? repairErr.message + : String(repairErr), + }; + await workspaceOperationRecorder.recordOperation({ + phase: "workspace_finalize", + cwd: executionWorkspace.cwd, + metadata: { + adapterType: agent.adapterType, + executionTargetKind: executionTarget?.kind ?? "local", + ...metadata, + managedGitWorktreeBranch: finalizeBranchMetadata, managedGitWorktreeBranchRepair: finalizeBranchRepairMetadata, - } - : {}), - }, - run: async () => ({ - status: "failed", - stderr: `Managed git worktree branch check failed: ${inspection.reason ?? "unknown branch mismatch"}\n`, - }), - }); - adapterFinalizeOutcome = "failed"; - throw new WorkspaceValidationFailure( - `Execution workspace ${branchInspection.workspaceRecord.id} expected git worktree branch "${inspection.expectedBranchName}" at "${inspection.worktreePath}", but ${inspection.reason ?? "the checked-out branch could not be verified"}. Record a sanctioned execution-workspace branch transition or restore the workspace branch before completing the run.`, - { - workspaceValidation: { - reason: "git_worktree_branch_incoherence", - fingerprint: workspaceValidationFingerprint, - adapterType: agent.adapterType, - issueId: issueRef?.id ?? null, - issueIdentifier: issueRef?.identifier ?? null, - persistedExecutionWorkspaceId: - branchInspection.workspaceRecord.id, - executionWorkspaceCwd: executionWorkspace.cwd, - managedGitWorktreeBranch: finalizeBranchMetadata, - }, - }, - ); - } - } - } - await workspaceOperationRecorder.recordOperation({ - phase: "workspace_finalize", - cwd: executionWorkspace.cwd, - metadata: { - adapterType: agent.adapterType, - executionTargetKind: executionTarget?.kind ?? "local", - ...metadata, - ...(finalizeBranchMetadata - ? { managedGitWorktreeBranch: finalizeBranchMetadata } - : {}), - ...(finalizeBranchRepairMetadata - ? { - managedGitWorktreeBranchRepair: - finalizeBranchRepairMetadata, + ...(workspaceValidationFailure?.resultJson + ? { + workspaceValidation: + workspaceValidationFailure.resultJson + .workspaceValidation ?? + workspaceValidationFailure.resultJson, + } + : {}), + }, + run: async () => ({ + status: "failed", + stderr: `Managed git worktree branch check failed: ${repairErr instanceof Error ? repairErr.message : String(repairErr)}\n`, + }), + }); + adapterFinalizeOutcome = "failed"; + throw repairErr; + } + + const repairedInspection = + await inspectManagedGitWorktreeBranch({ + worktreePath: inspection.worktreePath, + expectedBranchName: repairedExpectedBranchName, + repoRoot: inspection.repoRoot, + }); + finalizeBranchRepairMetadata = { + attempted: true, + succeeded: repairedInspection.valid, + initial: initialManagedGitWorktreeBranch, + repaired: + formatManagedGitWorktreeBranchInspection( + repairedInspection, + ), + }; + inspection = repairedInspection; } - : {}), + + const managedGitWorktreeBranch = + formatManagedGitWorktreeBranchInspection(inspection); + finalizeBranchMetadata = { + executionWorkspaceId: branchInspection.workspaceRecord.id, + ...managedGitWorktreeBranch, + }; + if (!inspection.valid) { + const workspaceValidationFingerprint = + fingerprintFinalizeWorkspaceBranchValidation({ + issueId: issueRef?.id ?? null, + executionWorkspaceId: branchInspection.workspaceRecord.id, + inspection: managedGitWorktreeBranch, + }); + await workspaceOperationRecorder.recordOperation({ + phase: "workspace_finalize", + cwd: executionWorkspace.cwd, + metadata: { + adapterType: agent.adapterType, + executionTargetKind: executionTarget?.kind ?? "local", + ...metadata, + managedGitWorktreeBranch: finalizeBranchMetadata, + ...(finalizeBranchRepairMetadata + ? { + managedGitWorktreeBranchRepair: + finalizeBranchRepairMetadata, + } + : {}), + }, + run: async () => ({ + status: "failed", + stderr: `Managed git worktree branch check failed: ${inspection.reason ?? "unknown branch mismatch"}\n`, + }), + }); + adapterFinalizeOutcome = "failed"; + throw new WorkspaceValidationFailure( + `Execution workspace ${branchInspection.workspaceRecord.id} expected git worktree branch "${inspection.expectedBranchName}" at "${inspection.worktreePath}", but ${inspection.reason ?? "the checked-out branch could not be verified"}. Record a sanctioned execution-workspace branch transition or restore the workspace branch before completing the run.`, + { + workspaceValidation: { + reason: "git_worktree_branch_incoherence", + fingerprint: workspaceValidationFingerprint, + adapterType: agent.adapterType, + issueId: issueRef?.id ?? null, + issueIdentifier: issueRef?.identifier ?? null, + persistedExecutionWorkspaceId: + branchInspection.workspaceRecord.id, + executionWorkspaceCwd: executionWorkspace.cwd, + managedGitWorktreeBranch: finalizeBranchMetadata, + }, + }, + ); + } + } + } + return { status, metadata: { + ...(finalizeBranchMetadata ? { managedGitWorktreeBranch: finalizeBranchMetadata } : {}), + ...(finalizeBranchRepairMetadata ? { managedGitWorktreeBranchRepair: finalizeBranchRepairMetadata } : {}), + } }; }, - run: async () => ({ status }), }); // Only mark the outcome after the row landed, so a transient write // failure on the succeeded path can still be recovered by recording @@ -919,10 +913,11 @@ export async function executeHeartbeatRuntime(db: Db, input: HeartbeatRuntimeExe eq(workspaceOperations.status, "succeeded"), )).limit(1); if (exported.length) adapterFinalizeOutcome = "succeeded"; - else await restoreNativeWorkspaceBestEffort({ - db, runId: run.id, assertOwnership: ownership?.assertHeld, - restore: () => nativeWorkspaceSync!.restoreWorkspace(ownership?.assertHeld), - }); + else await recordWorkspaceFinalize(hasWorkspaceRestoreFailure(adapterResult.resultJson) ? "failed" : "succeeded", undefined, + async () => { await restoreNativeWorkspaceBestEffort({ + db, runId: run.id, assertOwnership: ownership?.assertHeld, + restore: () => nativeWorkspaceSync!.restoreWorkspace(ownership?.assertHeld), + }); }); } await ownership?.assertHeld(); await db diff --git a/server/src/services/heartbeat/runtime-selection.test.ts b/server/src/services/heartbeat/runtime-selection.test.ts index dbd7bbcb878..f5f2c8bb706 100644 --- a/server/src/services/heartbeat/runtime-selection.test.ts +++ b/server/src/services/heartbeat/runtime-selection.test.ts @@ -76,11 +76,15 @@ describe.skipIf(!support.supported)("heartbeat runtime selection boundary", () = it("persists the native input, completion contract, and coordinator before transferring cleanup ownership", async () => { const { input, run } = await fixture(); + input.workspace.onCheckpoint = vi.fn(async () => {}); + input.workspace.onPhase = async (_name, work) => work(); vi.mocked(prepareNativeWorkspaceSync).mockImplementationOnce(async args => { expect(await read(run.id)).toMatchObject({ runtimeMode: "native", nativeIssueId: input.task.issueRef!.id }); expect(await coordinators(run.id)).toHaveLength(1); expect(input.stopControllerLease).toHaveBeenCalledOnce(); expect(args).toMatchObject({ runId: run.id, workspaceId: run.id, workspaceLocalDir: input.workspace.executionWorkspace.cwd }); + expect(args.onCheckpoint).toBe(input.workspace.onCheckpoint); + expect(args.onPhase).toBe(input.workspace.onPhase); return null; }); const selected = await selectHeartbeatRuntime(db, input); diff --git a/server/src/services/heartbeat/runtime-selection.ts b/server/src/services/heartbeat/runtime-selection.ts index efe4bbe63e7..c179e948bcf 100644 --- a/server/src/services/heartbeat/runtime-selection.ts +++ b/server/src/services/heartbeat/runtime-selection.ts @@ -118,6 +118,8 @@ export interface HeartbeatRuntimeSelectionInput { activeEnvironmentLease: Pick[0], "lease">; isDotRun: boolean; projectRepositoryPaths: string[]; + onCheckpoint?: Parameters[0]["onCheckpoint"]; + onPhase?: Parameters[0]["onPhase"]; }; config: { runtimeConfig: Record; @@ -1014,6 +1016,8 @@ export async function selectHeartbeatRuntime(db: Db, input: HeartbeatRuntimeSele restartRecovery: runOptions.nativeRestartRecovery, sameRunRecovery: Boolean(runOptions.nativeLeaseOwner), resourceDisposition: providerResourceDispositionForRun, + onCheckpoint: input.workspace.onCheckpoint, + onPhase: input.workspace.onPhase, }); } else { const legacyWarmLifecycle = diff --git a/server/src/services/heartbeat/runtime-services.ts b/server/src/services/heartbeat/runtime-services.ts index 8f7d4a87646..83630e2904c 100644 --- a/server/src/services/heartbeat/runtime-services.ts +++ b/server/src/services/heartbeat/runtime-services.ts @@ -92,6 +92,7 @@ export function prepareHeartbeatRuntimeServiceIntents(input: { export async function startHeartbeatRuntimeServices(db: Db, input: RuntimeServiceContext & { persistedExecutionWorkspace: { id: string } | null; hostExecutionWorkspaceConfig: Record; + skipHostProvision?: boolean; adapterEnv: RuntimeInput["adapterEnv"]; workspaceOperationRecorder: RuntimeInput["recorder"]; }) { @@ -112,6 +113,7 @@ export async function startHeartbeatRuntimeServices(db: Db, input: RuntimeServic issueRef?.executionWorkspaceId ?? null, config: hostExecutionWorkspaceConfig, + skipHostProvision: input.skipHostProvision, adapterEnv, onLog, recorder: workspaceOperationRecorder, diff --git a/server/src/services/heartbeat/session-preparation.ts b/server/src/services/heartbeat/session-preparation.ts index a7845726dc0..0179e26c90a 100644 --- a/server/src/services/heartbeat/session-preparation.ts +++ b/server/src/services/heartbeat/session-preparation.ts @@ -34,7 +34,8 @@ export interface HeartbeatSessionPreparationInput { context: Record; issueRef: Pick | null; issueContext: Pick | null; - projectContext: Pick | null; + taskProjectContext: Pick | null; + workspaceProjectContext: Pick | null; wakeCommentId: string | null; acceptedPlanContinuationWake: boolean; acceptedPlanWakeRoutingDecision: unknown; @@ -73,7 +74,7 @@ function readNonEmptyString(value: unknown): string | null { /** Selects a resumable session before workspace preparation; acquires no runtime resources. */ export async function prepareHeartbeatSession(db: Db, input: HeartbeatSessionPreparationInput) { const { run, agent, getLatestAgentConfigRevision } = input; - const { context, issueRef, issueContext, projectContext, wakeCommentId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision } = input.task; + const { context, issueRef, issueContext, taskProjectContext, workspaceProjectContext, wakeCommentId, acceptedPlanContinuationWake, acceptedPlanWakeRoutingDecision } = input.task; const { taskSession, taskSessionDecodedParams, explicitResumeSessionParams, explicitResumeSessionDisplayId, sessionCodec } = input.session; const { runtimeConfig, agentIdentity, issueAssigneeOverrides, originalAiIssueOverrides, managedAiRuntime, aiBinding, requestedAiBinding, secretManifest, runtimeSkillEntries, routineEnvContext } = input.config; const { requestedExecutionWorkspaceMode, effectiveExecutionWorkspaceMode, projectExecutionWorkspacePolicy, issueExecutionWorkspaceSettings, executionPolicy, requestedReusableExecutionWorkspaceConfig, reusableExistingExecutionWorkspace } = input.workspace; @@ -97,9 +98,9 @@ export async function prepareHeartbeatSession(db: Db, input: HeartbeatSessionPre ? issueContext.updatedAt.toISOString() : (issueContext?.updatedAt ?? null), projectConfigRevisionAt: - projectContext?.updatedAt instanceof Date - ? projectContext.updatedAt.toISOString() - : (projectContext?.updatedAt ?? null), + workspaceProjectContext?.updatedAt instanceof Date + ? workspaceProjectContext.updatedAt.toISOString() + : (workspaceProjectContext?.updatedAt ?? null), projectPolicy: projectExecutionWorkspacePolicy, issueSettings: issueExecutionWorkspaceSettings, reusableExecutionWorkspaceConfig: @@ -135,7 +136,7 @@ export async function prepareHeartbeatSession(db: Db, input: HeartbeatSessionPre executionPolicy, }, environmentEnv: selectedEnvironmentForConfig?.envVars ?? null, - projectEnv: projectContext?.env ?? null, + projectEnv: taskProjectContext?.env ?? null, routineEnv: routineEnvContext.env, secretManifest, runtimeSkills: runtimeSkillEntries, @@ -185,7 +186,7 @@ export async function prepareHeartbeatSession(db: Db, input: HeartbeatSessionPre acceptedPlanContinuationWake && !acceptedPlanWakeRoutingDecision && !agentIdentity, }); const resetTaskSession = - shouldResetTaskSessionForWake(context) || sessionConfigFreshness.reset; + context.workspaceSelectionChanged === true || shouldResetTaskSessionForWake(context) || sessionConfigFreshness.reset; const sessionResetReason = sessionConfigFreshness.reasons.join("; ") || null; const taskSessionForRun = resetTaskSession ? null : taskSession; diff --git a/server/src/services/heartbeat/workspace-preparation.test.ts b/server/src/services/heartbeat/workspace-preparation.test.ts index c251f4e02a9..dbacebdb8f2 100644 --- a/server/src/services/heartbeat/workspace-preparation.test.ts +++ b/server/src/services/heartbeat/workspace-preparation.test.ts @@ -1,19 +1,23 @@ import { execFile as execFileCallback } from "node:child_process"; -import { mkdtemp, mkdir, readFile, rm, writeFile, access } from "node:fs/promises"; +import { mkdtemp, mkdir, readFile, readdir, rm, writeFile, access, rename } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { pathToFileURL } from "node:url"; import { promisify } from "node:util"; -import { agents, companies, createDb, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, environmentLeases, heartbeatRuns, issues, projects, projectWorkspaces, type Db } from "@paperclipai/db"; +import { captureDirectorySnapshot, directorySnapshotSha256, serializeDirectorySnapshot } from "@paperclipai/adapter-utils/workspace-restore-merge"; +import type { AdapterExecutionTarget } from "@paperclipai/adapter-utils/execution-target"; +import { agents, companies, createDb, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, environmentLeases, executionWorkspaces, executionWorkspaceRepositoryAccess, heartbeatRuns, issues, issueAccessGrants, projects, projectWorkspaces, type Db } from "@paperclipai/db"; import { eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js"; import { environmentRunOrchestrator } from "../environment-run-orchestrator.js"; import { environmentService } from "../environments.js"; +import { assertExecutionWorkspaceRootReadAccess } from "../execution-workspace-repositories.js"; import { executionWorkspaceService } from "../execution-workspaces.js"; import { instanceSettingsService } from "../instance-settings.js"; -import { issueService } from "../issues.js"; import { workspaceOperationService } from "../workspace-operations.js"; -import { createHeartbeatWorkspaceResolver, resolveExecutionWorkspaceReuseRequestForIssue } from "./workspaces.js"; +import { nativeWorkspaceSyncInternals, prepareNativeWorkspaceSync } from "../native-runtime/native-workspace-sync.js"; +import { createHeartbeatWorkspaceResolver, prepareProjectRepositoryWorkspaces, resolveExecutionWorkspaceReuseRequestForIssue } from "./workspaces.js"; import { prepareHeartbeatWorkspace, type HeartbeatWorkspacePreparationInput } from "./workspace-preparation.js"; const execFile = promisify(execFileCallback); @@ -63,7 +67,7 @@ describe.skipIf(!support.supported)("heartbeat workspace preparation boundary", const resolver = createHeartbeatWorkspaceResolver(db); const input: HeartbeatWorkspacePreparationInput = { run, agent, - task: { issueRef: issue, issueId: issue.id, context: { issueId: issue.id, projectId: project.id }, executionProjectId: project.id, responsibleUserId: null, previousSessionParams: null }, + task: { issueRef: issue, issueId: issue.id, context: { issueId: issue.id, projectId: project.id }, responsibleUserId: null, previousSessionParams: null }, policy: { trustPreset: { kind: "standard", preset: "standard", boundary: null, sourcePresets: {} }, isolatedWorkspacesEnabled: true, @@ -76,7 +80,7 @@ describe.skipIf(!support.supported)("heartbeat workspace preparation boundary", environment: { selectedEnvironmentId: localEnvironment.id, localEnvironment, selectedEnvironmentForConfig: localEnvironment, environmentResolution: { environmentId: localEnvironment.id, source: "default" }, resolvedInstanceSettings: await instanceSettingsService(db).get() }, config: { mergedConfig: isolated ? { workspaceStrategy: { type: "git_worktree", baseRef: "main", branchTemplate: "test-{{issue.identifier}}" } } : {}, configSnapshot: null, secretManifest: [] }, reuse: { requestedShouldReuseExisting: false, existingExecutionWorkspace: null, reusableExistingExecutionWorkspace: null, workspaceReuseRequest: resolveExecutionWorkspaceReuseRequestForIssue({ issueExecutionWorkspaceId: null, issueExecutionWorkspacePreference: null, existingExecutionWorkspaceStatus: null }), nativeRecoveryExecutionWorkspaceId: null, persistedNativeExecutionInput: null, isDotRun: false, runOptions: {} }, - services: { envOrchestrator, executionWorkspacesSvc: executionWorkspaceService(db), workspaceOperationsSvc: workspaceOperationService(db), issuesSvc: issueService(db), ...resolver, appendRunEvent: vi.fn() }, + services: { envOrchestrator, executionWorkspacesSvc: executionWorkspaceService(db), workspaceOperationsSvc: workspaceOperationService(db), ...resolver, appendRunEvent: vi.fn() }, controllerLease: { assertOwned: vi.fn(async () => {}) }, nativeRunnerPreparationSpans: [], }; return { input, source, issue, run, envOrchestrator }; @@ -108,9 +112,135 @@ describe.skipIf(!support.supported)("heartbeat workspace preparation boundary", expect(result.persistedExecutionWorkspace).toMatchObject({ companyId: issue.companyId, projectId: issue.projectId, projectWorkspaceId: issue.projectWorkspaceId, mode: "shared_workspace" }); expect(await leases(input.run.id)).toMatchObject([{ status: "active", executionWorkspaceId: result.persistedExecutionWorkspace!.id }]); expect(input.nativeRunnerPreparationSpans.map(span => span.name)).toEqual(["environment.acquire", "environment.workspace.realize"]); + expect(input.nativeRunnerPreparationSpans.every(span => span.parentName === "task.provider_session")).toBe(true); expect(input.controllerLease.assertOwned).toHaveBeenCalledTimes(2); }); + it.each(["sandbox", "ssh", "local", null] as const)("uses the selected %s placement for controller provisioning", async driver => { + const { input, source, issue, envOrchestrator } = await fixture(true); + const marker = join(source, "host-provision-ran"); + const provisionCommand = `printf host > '${marker}'`; + input.config.mergedConfig = { workspaceStrategy: { type: "git_worktree", provisionCommand, runtimeProvisionCommand: "printf runtime" } }; + const originalConfig = structuredClone(input.config.mergedConfig); + input.environment.selectedEnvironmentForConfig = driver === null ? null : { ...input.environment.localEnvironment, driver }; + const stopped = new Error("Fixture stops before transport acquisition"); + vi.spyOn(envOrchestrator, "acquireForRun").mockRejectedValue(stopped); + await expect(prepareHeartbeatWorkspace(db, input)).rejects.toBe(stopped); + const bound = await readIssue(issue.id); + const [workspace] = await db.select().from(executionWorkspaces).where(eq(executionWorkspaces.id, bound.executionWorkspaceId!)); + expect(await readFile(join(workspace.cwd!, "README.md"), "utf8")).toBe("source content\n"); + if (driver === "sandbox" || driver === "ssh") await expect(readFile(marker, "utf8")).rejects.toMatchObject({ code: "ENOENT" }); + else expect(await readFile(marker, "utf8")).toBe("host"); + expect(input.config.mergedConfig).toEqual(originalConfig); + }); + + it("checks physical root admission before publishing configured sibling repositories", async () => { + const { input, source, issue } = await fixture(); + await db.insert(projectWorkspaces).values({ companyId: issue.companyId, projectId: issue.projectId!, + name: "Sibling repository", repoUrl: pathToFileURL(source).href }); + vi.spyOn(input.services.executionWorkspacesSvc, "prepareTaskRepositoriesForAdmission") + .mockRejectedValue(new Error("Physical root reader cannot access this source")); + const acquire = vi.spyOn(input.services.envOrchestrator, "acquireForRun"); + await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow("Physical root reader cannot access this source"); + await expect(access(join(source, ".paperclip-repositories"))).rejects.toMatchObject({ code: "ENOENT" }); + expect(acquire).not.toHaveBeenCalled(); + }); + + it("rechecks physical root access after configured clone and before publication", async () => { + const { input, source, issue } = await fixture(); + await db.insert(projectWorkspaces).values({ companyId: issue.companyId, projectId: issue.projectId!, + name: "Sibling repository", repoUrl: pathToFileURL(source).href }); + const namespace = join(source, ".paperclip-repositories"); + const guard = vi.spyOn(input.services.executionWorkspacesSvc, "guardTaskRepositoryRootForPublication") + .mockImplementation(async () => { + const candidates = await readdir(namespace); + expect(candidates).toHaveLength(1); + expect(candidates[0]).toContain(".clone-"); + expect(await readFile(join(namespace, candidates[0]!, "README.md"), "utf8")).toBe("source content\n"); + throw new Error("Physical root access revoked during clone"); + }); + await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow("Physical root access revoked during clone"); + expect(guard).toHaveBeenCalledOnce(); + expect(await readdir(namespace)).toEqual([]); + expect(await leases(input.run.id)).toEqual([]); + }); + + it("retains sibling privacy before copying when its source disappears before publication", async () => { + const previousPrivacyMode = process.env.PAPERCLIP_ISSUE_PRIVACY_MODE; + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", "enforce"); + try { + const { input, source, issue } = await fixture(); + const sibling = source + "-private-sibling"; + await execFile("git", ["clone", "--no-hardlinks", source, sibling]); + await writeFile(join(sibling, "private.txt"), "PRIVATE_SIBLING_STAGING_BYTES"); + const [privateIssue] = await db.insert(issues).values({ companyId: issue.companyId, title: "Private sibling owner", + visibility: "private", responsibleUserId: "private-owner" }).returning(); + const [privateWorkspace] = await db.insert(executionWorkspaces).values({ companyId: issue.companyId, + name: "Private sibling", projectId: null, sourceIssueId: privateIssue.id, cwd: sibling, + providerType: "local_fs", strategyType: "project_primary", mode: "shared_workspace", status: "active" }).returning(); + await db.insert(issueAccessGrants).values({ issueId: privateIssue.id, subjectType: "agent", subjectId: input.agent.id, source: "explicit" }); + const [configured] = await db.insert(projectWorkspaces).values({ companyId: issue.companyId, projectId: issue.projectId!, + name: "Private sibling", cwd: sibling, repoUrl: pathToFileURL(sibling).href }).returning(); + const namespace = join(source, ".paperclip-repositories"); + const originalPrepare = input.services.executionWorkspacesSvc.prepareConfiguredRepositorySource; + const prepare = vi.spyOn(input.services.executionWorkspacesSvc, "prepareConfiguredRepositorySource").mockImplementation(async args => { + const recheck = await originalPrepare(args); + return async () => { + const candidates = await readdir(namespace); + expect(candidates).toHaveLength(1); + expect(candidates[0]).toContain(".clone-"); + expect(await readFile(join(namespace, candidates[0]!, "private.txt"), "utf8")).toBe("PRIVATE_SIBLING_STAGING_BYTES"); + const retained = await db.select().from(executionWorkspaceRepositoryAccess).where(eq(executionWorkspaceRepositoryAccess.companyId, issue.companyId)); + expect(retained).toContainEqual(expect.objectContaining({ kind: "isolated_source", scopeId: privateWorkspace.id })); + await rename(sibling, sibling + "-moved"); + await db.update(projectWorkspaces).set({ cwd: sibling + "-moved" }).where(eq(projectWorkspaces.id, configured.id)); + await recheck(); + }; + }); + await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow(/ENOENT|unavailable|inaccessible|no longer/i); + expect(prepare).toHaveBeenCalledOnce(); + expect(await readdir(namespace)).toEqual([]); + expect(await leases(input.run.id)).toEqual([]); + await db.delete(issueAccessGrants).where(eq(issueAccessGrants.issueId, privateIssue.id)); + await expect(assertExecutionWorkspaceRootReadAccess(db, { companyId: issue.companyId, cwd: source, + actor: { type: "agent", source: "agent_jwt", companyId: issue.companyId, agentId: input.agent.id } })).rejects.toThrow(); + } finally { + vi.stubEnv("PAPERCLIP_ISSUE_PRIVACY_MODE", previousPrivacyMode); + } + }); + + it.each(["file://untrusted-host/private/repository", "file:///private%2Frepository", "file:///private/repository?other", "file:///private/repository#other"])("rejects malformed local repository URL before source access: %s", async repoUrl => { + const { source, issue } = await fixture(); + const prepareLocalSource = vi.fn(); + await expect(prepareProjectRepositoryWorkspaces({ cwd: source, anchorRepoUrl: null, + workspaces: [{ id: issue.projectWorkspaceId!, repoUrl, repoRef: null, cwd: null }], prepareLocalSource })).rejects.toThrow(); + expect(prepareLocalSource).not.toHaveBeenCalled(); + expect(await readdir(join(source, ".paperclip-repositories"))).toEqual([]); + }); + + it("persists and reuses task-owned files without adopting the organizational project as their source", async () => { + const { input, issue } = await fixture(); + await db.update(issues).set({ projectWorkspaceId: null, + workspaceSelection: { version: 1, source: "explicit", selection: { kind: "task_directory" } }, + }).where(eq(issues.id, issue.id)); + input.task.issueRef = await readIssue(issue.id); + const bind = vi.spyOn(input.services.executionWorkspacesSvc, "bindTaskWorkspace"); + const first = await prepareHeartbeatWorkspace(db, input); + expect(first.persistedExecutionWorkspace).toMatchObject({ projectId: null, projectWorkspaceId: null, sourceIssueId: issue.id }); + expect(bind).toHaveBeenCalled(); + expect((await readIssue(issue.id)).executionWorkspaceId).toBe(first.persistedExecutionWorkspace!.id); + await writeFile(join(first.executionWorkspace.cwd, "retained.txt"), "task files"); + const [organization] = await db.insert(projects).values({ companyId: issue.companyId, name: "New organization" }).returning(); + await db.update(issues).set({ projectId: organization.id }).where(eq(issues.id, issue.id)); + input.task.issueRef = await readIssue(issue.id); + await reuse(input, first.persistedExecutionWorkspace!); + const next = await prepareHeartbeatWorkspace(db, input); + expect(next.persistedExecutionWorkspace).toMatchObject({ id: first.persistedExecutionWorkspace!.id, projectId: null }); + expect(next.executionWorkspace.cwd).toBe(first.executionWorkspace.cwd); + expect(await readFile(join(next.executionWorkspace.cwd, "retained.txt"), "utf8")).toBe("task files"); + expect((await readIssue(issue.id)).projectId).toBe(organization.id); + }); + it("creates a real isolated worktree and reuses its recorded workspace", async () => { const { input, source, issue } = await fixture(true); const first = await prepareHeartbeatWorkspace(db, input); @@ -133,12 +263,94 @@ describe.skipIf(!support.supported)("heartbeat workspace preparation boundary", input.task.issueRef = { ...input.task.issueRef!, executionWorkspaceId: null, projectWorkspaceId: null }; input.reuse.nativeRecoveryExecutionWorkspaceId = first.persistedExecutionWorkspace!.id; const anchor = vi.spyOn(input.services, "resolveReusedGitWorkspaceAnchor"); + const prepareRepositories = vi.spyOn(input.services.executionWorkspacesSvc, "prepareTaskRepositoriesForAdmission"); + const reserveRoot = vi.spyOn(input.services.executionWorkspacesSvc, "reserveTaskRepositoryRootForRecovery"); const next = await prepareHeartbeatWorkspace(db, input); + expect(reserveRoot).toHaveBeenCalledWith({ companyId: issue.companyId, issueId: issue.id, + workspaceId: first.persistedExecutionWorkspace!.id, cwd: first.executionWorkspace.cwd, + agentId: input.agent.id, runId: input.run.id }); + expect(prepareRepositories).not.toHaveBeenCalled(); expect(next.persistedExecutionWorkspace!.id).toBe(first.persistedExecutionWorkspace!.id); expect(anchor).toHaveBeenCalledWith(expect.objectContaining({ immutableNativeBinding: true, explicitProjectWorkspaceId: first.persistedExecutionWorkspace!.projectWorkspaceId })); expect(await readIssue(issue.id)).toMatchObject({ executionWorkspaceId: null, projectWorkspaceId: null }); }); + it.each([ + { transport: "local", descriptor: "absent" }, + { transport: "ssh", descriptor: "absent" }, + { transport: "daytona", descriptor: "valid" }, + { transport: "daytona", descriptor: "absent" }, + { transport: "daytona", descriptor: "corrupt" }, + ] as const)("reserves the admitted host root for $transport recovery with $descriptor descriptor", async ({ transport, descriptor }) => { + const { input, issue } = await fixture(true); + const first = await prepareHeartbeatWorkspace(db, input); + await reuse(input, first.persistedExecutionWorkspace!); + await db.update(issues).set({ executionWorkspaceId: null, projectWorkspaceId: null }).where(eq(issues.id, issue.id)); + input.task.issueRef = { ...input.task.issueRef!, executionWorkspaceId: null, projectWorkspaceId: null }; + input.reuse.nativeRecoveryExecutionWorkspaceId = first.persistedExecutionWorkspace!.id; + const admittedRoot = join(home, `admitted-${input.run.id}`); + await mkdir(admittedRoot); + const remoteCwd = "/remote/admitted-task"; + input.reuse.persistedNativeExecutionInput = { + schema: "paperclip.native-execution-input.v1", + binding: { companyId: issue.companyId, runId: input.run.id, issueId: issue.id, + agentId: input.agent.id, executionWorkspaceId: first.persistedExecutionWorkspace!.id }, + task: { identifier: "WSP-1", title: issue.title, description: null, prompt: "Continue admitted work", workMode: "standard" }, + workspace: { cwd: transport === "local" ? admittedRoot : remoteCwd, repoUrl: null, repoRef: null, branchName: null }, + session: { normalizedSessionId: null, driverKind: "codex_app_server", protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null } }, + provider: { kind: "codex", model: null }, + completionContract: { id: "contract", sha256: "abc123", schemaVersion: "paperclip.completion-contract.v1", + contract: { revision: "1", objective: "Continue admitted work", criteria: [{ id: "objective", requirement: "Finish" }] } }, + interactionResponses: [], credentialBindings: [], + }; + const target: AdapterExecutionTarget = transport === "local" ? { kind: "local" } + : transport === "ssh" ? { kind: "remote", transport: "ssh", remoteCwd, + spec: { host: "fixture.invalid", port: 22, username: "fixture", remoteWorkspacePath: remoteCwd, + privateKey: null, knownHosts: null, strictHostKeyChecking: true, remoteCwd } } + : { kind: "remote", transport: "sandbox", providerKey: "daytona", remoteCwd }; + const realize = input.services.envOrchestrator.realizeForRun; + vi.spyOn(input.services.envOrchestrator, "realizeForRun").mockImplementation(async args => ({ + ...await realize(args), executionTarget: target, + })); + if (descriptor !== "absent") { + const baseline = await captureDirectorySnapshot(admittedRoot); + const reference = await nativeWorkspaceSyncInternals.writeDescriptor({ + schema: "paperclip.native-workspace-sync/v1", state: "prepared", + binding: { companyId: issue.companyId, runId: input.run.id, workspaceId: first.persistedExecutionWorkspace!.id, + leaseId: "admitted-lease", providerLeaseId: "admitted-provider", localCwd: admittedRoot, remoteCwd }, + baseline: serializeDirectorySnapshot(baseline), baselineSha256: directorySnapshotSha256(baseline), + gitSnapshot: null, seed: null, createdAt: new Date().toISOString(), finalizedAt: null, finalHostSha256: null, resourceDisposition: null, + }); + input.run.runnerProfileJson = { nativeWorkspaceSync: reference }; + if (descriptor === "corrupt") { + await writeFile(nativeWorkspaceSyncInternals.descriptorPath(input.run.id, reference.descriptorSha256), "{}"); + } + } + const reserve = vi.spyOn(input.services.executionWorkspacesSvc, "reserveTaskRepositoryRootForRecovery"); + const prepare = vi.spyOn(input.services.executionWorkspacesSvc, "prepareTaskRepositoriesForAdmission"); + if (transport !== "daytona" || descriptor === "valid") { + const result = await prepareHeartbeatWorkspace(db, input); + expect(result.executionTarget).toEqual(target); + if (transport === "ssh") expect(reserve).not.toHaveBeenCalled(); + else expect(reserve).toHaveBeenCalledWith({ companyId: issue.companyId, issueId: issue.id, + workspaceId: first.persistedExecutionWorkspace!.id, cwd: admittedRoot, agentId: input.agent.id, runId: input.run.id }); + if (transport !== "daytona") { + await expect(prepareNativeWorkspaceSync({ db, runId: input.run.id, companyId: issue.companyId, + workspaceId: first.persistedExecutionWorkspace!.id, workspaceLocalDir: first.executionWorkspace.cwd, + target, lease: result.activeEnvironmentLease.lease })).resolves.toBeNull(); + } + expect(admittedRoot).not.toBe(first.executionWorkspace.cwd); + } else { + await expect(prepareHeartbeatWorkspace(db, input)).rejects.toThrow(descriptor === "absent" + ? "native_workspace_sync_descriptor_binding_missing" : "native_workspace_sync_descriptor_digest_mismatch"); + expect(reserve).not.toHaveBeenCalled(); + expect(await leases(input.run.id)).toMatchObject([{ status: "active" }]); + } + expect(prepare).not.toHaveBeenCalled(); + expect((await readIssue(issue.id)).executionWorkspaceId).toBeNull(); + }); + it("removes a newly created worktree when persistence fails and preserves the original error", async () => { const { input, source } = await fixture(true); const error = new Error("workspace persistence unavailable"); diff --git a/server/src/services/heartbeat/workspace-preparation.ts b/server/src/services/heartbeat/workspace-preparation.ts index cb699b94820..9d0a5a7ba1c 100644 --- a/server/src/services/heartbeat/workspace-preparation.ts +++ b/server/src/services/heartbeat/workspace-preparation.ts @@ -1,7 +1,7 @@ import { resolveWorkspaceAfterLowTrustPreflight, WorkspaceValidationFailure, - stripHostWorkspaceProvisionForLowTrustSandbox, + stripHostWorkspaceProvision, assertGitWorktreeBaseWorkspaceReady, buildEffectiveRunWorkspaceConfigMetadata, resolveExecutionWorkspaceConfigFreshness, @@ -16,6 +16,7 @@ import { import { resolvePaperclipInstanceRoot } from "../../home-paths.js"; import fs from "node:fs/promises"; import path from "node:path"; +import { CONTROLLER_PROVISION_COPY_ONLY, workspaceRequiresCopyOnlyProvisioning } from "../workspace-host-provision.js"; import { and, asc, eq } from "drizzle-orm"; import { type ExecutionWorkspace } from "@paperclipai/shared"; import { heartbeatRuns, projectWorkspaces } from "@paperclipai/db"; @@ -24,10 +25,14 @@ import { createHostDuplexObservabilityRecorder } from "../duplex-observability-r import { incrementToolRuntimeMetricCounter } from "../tool-runtime-metrics.js"; import { logger } from "../../middleware/logger.js"; import { createGitRemoteAuthProvider } from "../git-credentials.js"; -import { readNativeWorkspaceSyncReference } from "../native-runtime/index.js"; +import { readNativeWorkspaceSyncReference, readNativeWorkspaceSyncLocalCwd } from "../native-runtime/index.js"; import { parseObject } from "../../adapters/utils.js"; import { materializeNativeChatTaskRoot } from "../native-runtime/native-chat-workspace.js"; import { materializeIsolatedTaskDirectory } from "../isolated-task-directory.js"; +import { + assertIsolatedWorkspaceSourceReadAccess, + retainIsolatedWorkspaceSourceAccess, +} from "../execution-workspace-repositories.js"; import { cleanupExecutionWorkspaceArtifacts, ensurePersistedExecutionWorkspaceAvailable, @@ -58,8 +63,7 @@ import type { NativeChatWorkspaceScope } from "../native-runtime/native-chat-wor import type { TrustPresetResolution } from "../trust-preset-resolver.js"; import type { environmentRunOrchestrator } from "../environment-run-orchestrator.js"; import type { instanceSettingsService } from "../instance-settings.js"; -import type { issueService } from "../issues.js"; -import type { executionWorkspaceService } from "../execution-workspaces.js"; +import type { executionWorkspaceService, TaskWorkspaceBindingPatch } from "../execution-workspaces.js"; import type { workspaceOperationService } from "../workspace-operations.js"; import type { watchLegacyControllerLease } from "../legacy-controller-lease.js"; import type { @@ -88,9 +92,9 @@ export interface HeartbeatWorkspacePreparationInput { }) | null; issueId: string | null; context: Record; - executionProjectId: string | null; responsibleUserId: string | null; previousSessionParams: Record | null; + workspaceSelection?: import("@paperclipai/shared").TaskWorkspaceIntent | null; }; policy: { trustPreset: TrustPresetResolution; @@ -128,9 +132,8 @@ export interface HeartbeatWorkspacePreparationInput { }; services: { envOrchestrator: Pick, "resolveEnvironment" | "acquireForRun" | "realizeForRun">; - executionWorkspacesSvc: Pick, "create" | "update">; + executionWorkspacesSvc: Pick, "create" | "update" | "bindTaskWorkspace" | "prepareTaskRepositoriesForAdmission" | "guardTaskRepositoryRootForPublication" | "prepareConfiguredRepositorySource" | "reserveTaskRepositoryRootForRecovery">; workspaceOperationsSvc: Pick, "createRecorder">; - issuesSvc: Pick, "update">; resolveWorkspaceForRun: WorkspaceResolver["resolveWorkspaceForRun"]; resolveReusedGitWorkspaceAnchor: WorkspaceResolver["resolveReusedGitWorkspaceAnchor"]; appendRunEvent: ReturnType["appendRunEvent"]; @@ -149,13 +152,12 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac issueRef, issueId, context, - executionProjectId, responsibleUserId, previousSessionParams, } = input.task; const { trustPreset, - isolatedWorkspacesEnabled, + isolatedWorkspacesEnabled: runtimeWorkspaceSelectionEnabled, effectiveExecutionWorkspaceMode, requestedExecutionWorkspaceMode, useIsolatedTaskDirectory, @@ -187,7 +189,6 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac envOrchestrator, executionWorkspacesSvc, workspaceOperationsSvc, - issuesSvc, resolveWorkspaceForRun, resolveReusedGitWorkspaceAnchor, appendRunEvent, @@ -198,7 +199,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac } = await resolveWorkspaceAfterLowTrustPreflight({ db, trustPreset, - isolatedWorkspacesEnabled, + isolatedWorkspacesEnabled: runtimeWorkspaceSelectionEnabled, effectiveExecutionWorkspaceMode, issue: issueRef ? { @@ -233,7 +234,6 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac }); if (reusableExistingExecutionWorkspace && ( reusableExistingExecutionWorkspace.companyId !== agent.companyId || - reusableExistingExecutionWorkspace.projectId !== issueRef.projectId || reusableExistingExecutionWorkspace.sourceIssueId !== issueRef.id || reusableExistingExecutionWorkspace.mode !== "isolated_workspace" || reusableExistingExecutionWorkspace.strategyType !== "project_primary" || @@ -248,7 +248,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac anchorWorkspace: { cwd, source: "task_session", - projectId: issueRef.projectId, + projectId: reusableExistingExecutionWorkspace?.projectId ?? null, workspaceId: null, repoUrl: null, repoRef: null, @@ -285,6 +285,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac { useProjectWorkspace: requestedExecutionWorkspaceMode !== "agent_default", + configuredCwd: readNonEmptyString(mergedConfig.cwd), anchorWorkspace: requestedShouldReuseExisting && reusableExistingExecutionWorkspace?.strategyType === "git_worktree" ? await resolveReusedGitWorkspaceAnchor({ agent, @@ -300,7 +301,14 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac issueId, runId: run.id, }) - : undefined, + : requestedShouldReuseExisting && reusableExistingExecutionWorkspace?.cwd ? { + cwd: reusableExistingExecutionWorkspace.cwd, source: "task_session" as const, + projectId: reusableExistingExecutionWorkspace.projectId, + workspaceId: reusableExistingExecutionWorkspace.projectWorkspaceId, + repoUrl: reusableExistingExecutionWorkspace.repoUrl, repoRef: reusableExistingExecutionWorkspace.baseRef, + localPathOnlyWorkspace: reusableExistingExecutionWorkspace.strategyType === "project_primary" && !reusableExistingExecutionWorkspace.repoUrl && !reusableExistingExecutionWorkspace.branchName, + workspaceHints: [], warnings: [], baseCwdFallback: false, materializationFailures: [], + } : undefined, // Thread the selected environment driver so run-workspace resolution can tell a local // target from a remote one, and a confined sandbox target from an unconfined remote // target. A remote run resolves referenced projects only for the confined sandbox @@ -320,11 +328,19 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac : workspace; }, }); + const hostProvisionDriver = selectedEnvironmentForConfig?.driver ?? lowTrustPreflightEnvironmentDriver; + const copyOnlyProvisioning = await workspaceRequiresCopyOnlyProvisioning(db, { + companyId: agent.companyId, + task: issueRef ? { ...issueRef, workspaceSelection: input.task.workspaceSelection } : null, + sourceProjectId: resolvedWorkspace.projectId, + sourceWorkspaceId: resolvedWorkspace.workspaceId, + boundWorkspace: reusableExistingExecutionWorkspace ?? existingExecutionWorkspace, + }); + const skipHostProvision = copyOnlyProvisioning || hostProvisionDriver === "sandbox" || hostProvisionDriver === "ssh"; const hostExecutionWorkspaceConfig = isDotRun ? {} : - stripHostWorkspaceProvisionForLowTrustSandbox({ + stripHostWorkspaceProvision({ config: mergedConfig, - trustPreset, - selectedEnvironmentDriver: lowTrustPreflightEnvironmentDriver, + skipHostProvision, }); const executionWorkspaceBase = { baseCwd: resolvedWorkspace.cwd, @@ -466,6 +482,26 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac agentId: agent.id, }, ); + const isolatedWorkspaceSourceActor = { type: "agent" as const, source: "agent_jwt" as const, companyId: agent.companyId, + agentId: agent.id, runId: run.id, onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId }; + const isolatedWorkspaceSourceAccess = !isDotRun && issueId && !persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId + && (latestWorkspaceStrategyType === "git_worktree" + || (workspaceReuseProvisioningPolicy.shouldRestoreExistingWorkspace && reusableExistingExecutionWorkspace?.strategyType === "git_worktree")) + ? await assertIsolatedWorkspaceSourceReadAccess(db, { + companyId: agent.companyId, sourceCwd: executionWorkspaceBase.baseCwd, actor: isolatedWorkspaceSourceActor, + }) : null; + const beforeIsolatedSourceCopy = isolatedWorkspaceSourceAccess + ? (sourceCwd: string, targetCwd: string) => retainIsolatedWorkspaceSourceAccess(db, { + companyId: agent.companyId, sourceCwd, targetCwd, + actor: isolatedWorkspaceSourceActor, captured: isolatedWorkspaceSourceAccess, issueId: issueId ?? undefined, + phase: "preparing", + }) : undefined; + const afterIsolatedSourceCopy = isolatedWorkspaceSourceAccess + ? (sourceCwd: string, targetCwd: string) => retainIsolatedWorkspaceSourceAccess(db, { + companyId: agent.companyId, sourceCwd, targetCwd, + actor: isolatedWorkspaceSourceActor, captured: isolatedWorkspaceSourceAccess, issueId: issueId ?? undefined, + phase: "verified", + }) : undefined; const { executionWorkspace, reusedExecutionWorkspace, @@ -481,6 +517,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac restoreExistingWorkspace: reusableExistingExecutionWorkspace ? () => ensurePersistedExecutionWorkspaceAvailable({ + skipHostProvision, db, base: executionWorkspaceBase, workspace: { @@ -533,10 +570,13 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac .enableWorkspaceDirtyQuarantineRepair, recorder: workspaceOperationRecorder, resolveGitAuth: workspaceGitAuthProvider, + beforeSourceCopy: beforeIsolatedSourceCopy, + afterSourceCopy: afterIsolatedSourceCopy, }) : null, realizeWorkspace: () => realizeExecutionWorkspace({ + skipHostProvision, db, base: executionWorkspaceBase, config: hostExecutionWorkspaceConfig, @@ -565,14 +605,26 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac .enableWorkspaceDirtyQuarantineRepair, recorder: workspaceOperationRecorder, resolveGitAuth: workspaceGitAuthProvider, + beforeSourceCopy: beforeIsolatedSourceCopy, + afterSourceCopy: afterIsolatedSourceCopy, }), }, ); + if (isolatedWorkspaceSourceAccess && executionWorkspace.strategy === "git_worktree") { + await retainIsolatedWorkspaceSourceAccess(db, { + companyId: agent.companyId, + sourceCwd: executionWorkspaceBase.baseCwd, + targetCwd: executionWorkspace.cwd, + actor: isolatedWorkspaceSourceActor, + captured: isolatedWorkspaceSourceAccess, + issueId: issueId ?? undefined, + phase: "verified", + }); + } const resolvedProjectId = - executionWorkspace.projectId ?? - issueRef?.projectId ?? - executionProjectId ?? - null; + reusableExistingExecutionWorkspace + ? reusableExistingExecutionWorkspace.projectId + : executionWorkspace.projectId; const resolvedProjectWorkspaceId = resolvedWorkspaceReusePolicy.shouldRestoreExistingWorkspace && reusableExistingExecutionWorkspace?.strategyType === "git_worktree" ? reusableExistingExecutionWorkspace.projectWorkspaceId @@ -607,8 +659,8 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac issueRef?.executionWorkspacePreference === "reuse_existing" || requestedExecutionWorkspaceMode === "isolated_workspace" || requestedExecutionWorkspaceMode === "operator_branch" || - warmReusableExecutionWorkspace || nativeSharedWorkspace; - const nextIssuePatch: Record = {}; + warmReusableExecutionWorkspace || nativeSharedWorkspace || Boolean(issueId); + const nextIssuePatch: TaskWorkspaceBindingPatch = {}; if (issueExecutionWorkspaceIdForRun !== workspace.id) { nextIssuePatch.executionWorkspaceId = workspace.id; } @@ -630,14 +682,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac }; } if (Object.keys(nextIssuePatch).length > 0) { - await issuesSvc.update( - issueId, - { ...nextIssuePatch, companyGuard: agent.companyId }, - db, - undefined, - undefined, - { bindRuntimeSharedWorkspace: (warmReusableExecutionWorkspace || nativeSharedWorkspace) && workspace.mode === "shared_workspace" }, - ); + await executionWorkspacesSvc.bindTaskWorkspace(agent.companyId, issueId, workspace.id, nextIssuePatch); issueExecutionWorkspaceIdForRun = workspace.id; issueProjectWorkspaceIdForRun = resolvedProjectWorkspaceId ?? issueProjectWorkspaceIdForRun; @@ -706,6 +751,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac } const nextExecutionWorkspaceMetadata = { ...baseExecutionWorkspaceMetadata, + ...(copyOnlyProvisioning ? { [CONTROLLER_PROVISION_COPY_ONLY]: true } : {}), ...(persistedWorktreeInstanceRoot ? { [WORKTREE_INSTANCE_ROOT_METADATA_KEY]: @@ -744,7 +790,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac ), }, ) - : resolvedProjectId + : !isDotRun && !persistedNativeExecutionInput && (resolvedProjectId || issueRef) ? await executionWorkspacesSvc.create({ companyId: agent.companyId, projectId: resolvedProjectId, @@ -866,7 +912,15 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac } await bindIssueToPersistedExecutionWorkspace(persistedExecutionWorkspace); const projectRepositoryPaths: string[] = []; - if (executionWorkspace.projectId && resolvedWorkspace.source === "project_primary" && !resolvedWorkspace.baseCwdFallback) { + if (persistedExecutionWorkspace && issueId && !persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId) { + const taskRepositories = await executionWorkspacesSvc.prepareTaskRepositoriesForAdmission({ companyId: agent.companyId, + issueId, workspaceId: persistedExecutionWorkspace.id, cwd: executionWorkspace.cwd, + agentId: agent.id, runId: run.id, responsibleUserId }); + projectRepositoryPaths.push(...taskRepositories.map(repository => repository.relativePath)); + } + if (executionWorkspace.projectId + && (resolvedWorkspace.source === "project_primary" || Boolean(reusableExistingExecutionWorkspace?.projectWorkspaceId)) + && !resolvedWorkspace.baseCwdFallback && !persistedNativeExecutionInput && !nativeRecoveryExecutionWorkspaceId) { const repositoryRows = await db.select().from(projectWorkspaces).where(and( eq(projectWorkspaces.companyId, agent.companyId), eq(projectWorkspaces.projectId, executionWorkspace.projectId), @@ -876,12 +930,25 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac anchorRepoUrl: executionWorkspace.repoUrl, workspaces: repositoryRows, resolveGitAuth: workspaceGitAuthProvider, + prepareLocalSource: persistedExecutionWorkspace && issueId + ? (sourceCwd) => executionWorkspacesSvc.prepareConfiguredRepositorySource({ + companyId: agent.companyId, issueId, workspaceId: persistedExecutionWorkspace!.id, + cwd: executionWorkspace.cwd, agentId: agent.id, runId: run.id, sourceCwd, + }) : undefined, + beforePublish: persistedExecutionWorkspace && issueId && !nativeRecoveryExecutionWorkspaceId + ? () => executionWorkspacesSvc.guardTaskRepositoryRootForPublication({ + companyId: agent.companyId, issueId, workspaceId: persistedExecutionWorkspace!.id, + cwd: executionWorkspace.cwd, agentId: agent.id, runId: run.id, + }) : undefined, }); const paths = new Map(repositories.map((repo) => [repo.workspaceId, repo.cwd])); projectRepositoryPaths.push(...repositories.map((repo) => path.relative(executionWorkspace.cwd, repo.cwd))); if (resolvedWorkspace.workspaceId) paths.set(resolvedWorkspace.workspaceId, executionWorkspace.cwd); - resolvedWorkspace.workspaceHints = resolvedWorkspace.workspaceHints.map((hint) => ({ - ...hint, cwd: paths.get(hint.workspaceId) ?? hint.cwd, + resolvedWorkspace.workspaceHints = repositoryRows.map((source) => ({ + workspaceId: source.id, + cwd: paths.get(source.id) ?? readNonEmptyString(source.cwd), + repoUrl: readNonEmptyString(source.repoUrl), + repoRef: readNonEmptyString(source.repoRef), })); } if (persistedExecutionWorkspace) { @@ -930,7 +997,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac await controllerLease.assertOwned(); nativeRunnerPreparationSpans.push({ name: "environment.acquire", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: environmentAcquireStartedAtMs, endedAtMs: Date.now(), attributes: { adapter: agent.adapterType }, @@ -938,7 +1005,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac } catch (error) { nativeRunnerPreparationSpans.push({ name: "environment.acquire", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: environmentAcquireStartedAtMs, endedAtMs: Date.now(), outcome: "failed", @@ -1016,7 +1083,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac }); nativeRunnerPreparationSpans.push({ name: "environment.workspace.realize", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: environmentRealizeStartedAtMs, endedAtMs: Date.now(), attributes: { driver: selectedEnvironment.driver }, @@ -1024,7 +1091,7 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac } catch (error) { nativeRunnerPreparationSpans.push({ name: "environment.workspace.realize", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: environmentRealizeStartedAtMs, endedAtMs: Date.now(), outcome: "failed", @@ -1046,12 +1113,29 @@ export async function prepareHeartbeatWorkspace(db: Db, input: HeartbeatWorkspac await bindIssueToPersistedExecutionWorkspace(persistedExecutionWorkspace); const workspaceRealization = realizationResult.workspaceRealization; const executionTarget = realizationResult.executionTarget; + if (!isDotRun && issueId && (persistedNativeExecutionInput || nativeRecoveryExecutionWorkspaceId) + && !(executionTarget?.kind === "remote" && executionTarget.transport === "ssh")) { + const workspaceId = persistedNativeExecutionInput?.binding.executionWorkspaceId ?? nativeRecoveryExecutionWorkspaceId!; + const reference = readNativeWorkspaceSyncReference(parseObject(run.runnerProfileJson).nativeWorkspaceSync); + if (!reference && executionTarget?.kind === "remote" && executionTarget.transport === "sandbox") { + throw new Error("native_workspace_sync_descriptor_binding_missing"); + } + const cwd = reference + ? await readNativeWorkspaceSyncLocalCwd({ companyId: agent.companyId, runId: run.id, workspaceId, reference }) + : persistedNativeExecutionInput && persistedNativeExecutionInput.schema !== "paperclip.native-execution-input.v6" + && executionTarget?.kind !== "remote" + ? persistedNativeExecutionInput.workspace.cwd + : executionWorkspace.cwd; + await executionWorkspacesSvc.reserveTaskRepositoryRootForRecovery({ companyId: agent.companyId, + issueId, workspaceId, cwd, agentId: agent.id, runId: run.id }); + } // Preserve the host-owned source before adapter context can share lease // metadata. A later copy-back failure must not adopt a rebound source. const workspaceRestoreSource = structuredClone(realizationResult.lease); return { resolvedWorkspace, hostExecutionWorkspaceConfig, + skipHostProvision, latestWorkspaceConfigMetadata, workspaceConfigFreshness, workspaceOperationRecorder, diff --git a/server/src/services/heartbeat/workspaces.test.ts b/server/src/services/heartbeat/workspaces.test.ts index 7dc7f09f827..f06a1ba1160 100644 --- a/server/src/services/heartbeat/workspaces.test.ts +++ b/server/src/services/heartbeat/workspaces.test.ts @@ -106,6 +106,7 @@ describe("heartbeat workspace module", () => { await fs.mkdir(selectedCwd); const database = databaseWithResults( [{ projectId: "current-project", projectWorkspaceId: "selected-workspace" }], + [{ projectId: "current-project" }], [workspace("default-workspace", "current-project", path.join(testRoot, "missing")), workspace("selected-workspace", "current-project", selectedCwd)], ); const result = await createHeartbeatWorkspaceResolver(database.db).resolveWorkspaceForRun( @@ -114,7 +115,37 @@ describe("heartbeat workspace module", () => { null, ); expect(result).toMatchObject({ cwd: selectedCwd, projectId: "current-project", workspaceId: "selected-workspace", warnings: [], baseCwdFallback: false }); - expect(database.select).toHaveBeenCalledTimes(2); + expect(database.select).toHaveBeenCalledTimes(3); + }); + + it("fails closed with workspace diagnostics when a configured directory is unavailable", async () => { + const missingCwd = path.join(testRoot, "missing-configured-workspace"); + const database = databaseWithResults([workspace("selected", "project-one", missingCwd)]); + await expect(createHeartbeatWorkspaceResolver(database.db).resolveWorkspaceForRun( + agent(), { projectId: "project-one" }, null, + )).rejects.toMatchObject({ + code: "workspace_validation_failed", + resultJson: { workspaceValidation: { + reason: "configured_workspace_unavailable", resolvedProjectId: "project-one", baseCwdFallback: false, + } }, + }); + expect(await fs.stat(missingCwd).catch(() => null)).toBeNull(); + }); + + it("retains materialization diagnostics when a configured repository cannot be cloned", async () => { + const missingRepo = path.join(testRoot, "missing-repository.git"); + const database = databaseWithResults([{ + ...workspace("selected", "project-one", ""), repoUrl: missingRepo, sourceType: "git_remote", + }]); + await expect(createHeartbeatWorkspaceResolver(database.db).resolveWorkspaceForRun( + agent(), { projectId: "project-one" }, null, + )).rejects.toMatchObject({ + code: "workspace_validation_failed", + resultJson: { workspaceValidation: { + reason: "git_worktree_base_materialization_failed", + materializationFailures: [expect.objectContaining({ projectWorkspaceId: "selected", repoUrl: missingRepo, error: expect.any(String) })], + } }, + }); }); it("retains a valid prior session workspace and falls back when it disappears", async () => { diff --git a/server/src/services/heartbeat/workspaces.ts b/server/src/services/heartbeat/workspaces.ts index 69b879f3c28..1c37505aa95 100644 --- a/server/src/services/heartbeat/workspaces.ts +++ b/server/src/services/heartbeat/workspaces.ts @@ -1,22 +1,19 @@ +import { assertIsolatedWorkspaceSourceReadAccess, retainIsolatedWorkspaceSourceAccess } from "../execution-workspace-repositories.js"; +import { forbidden } from "../../errors.js"; +import { readGitConnectionFailure } from "../git-connection-failure.js"; +import { materializeManagedProjectWorkspace, ensureManagedRepositoriesIgnored } from "../managed-repository-checkout.js"; +export { materializeManagedProjectWorkspace } from "../managed-repository-checkout.js"; +import { sql } from "drizzle-orm"; +import { materializeIsolatedTaskDirectory } from "../isolated-task-directory.js"; import { readConnectionFailure, type GitConnectionFailure } from "@paperclipai/adapter-utils/connection-failure"; -import { classifyGitCloneFailure, GitConnectionFailureError, readGitConnectionFailure } from "../git-connection-failure.js"; import { managedAiSessionFingerprintConfig } from "../ai-connection-runtime.js"; import { PROJECT_REPOSITORIES_DIR, readGitWorkspaceSnapshot, - disposeGitWorkspaceSnapshot, } from "@paperclipai/adapter-utils/git-workspace-sync"; -import { - isWorkspaceGitScanError, - WorkspaceGitScanError, -} from "../workspace-git-operation-scheduler.js"; -import { - captureDirectorySnapshot, - disposeDirectorySnapshot, - mergeDirectoryWithBaseline, -} from "@paperclipai/adapter-utils/workspace-restore-merge"; import fs from "node:fs/promises"; import path from "node:path"; +import { localRepositoryPath } from "../local-repository-path.js"; import { execFile as execFileCallback } from "node:child_process"; import { promisify } from "node:util"; import { @@ -43,7 +40,6 @@ import { import { logger } from "../../middleware/logger.js"; import { createGitRemoteAuthProvider, - describeGitAuthFailure, scrubGitCredentialText, type GitRemoteAuthProvider, } from "../git-credentials.js"; @@ -61,7 +57,6 @@ import { inspectManagedGitWorktreeBranch, type ExecutionWorkspaceInput, type RealizedExecutionWorkspace, - sanitizeRuntimeServiceBaseEnv, } from "../workspace-runtime.js"; import { resolvePersistedGitWorkspaceSource } from "../persisted-workspace-source.js"; import { issueService } from "../issues.js"; @@ -97,7 +92,6 @@ const execFile = promisify(execFileCallback); const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__"; -const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000; export const WORKSPACE_VALIDATION_FAILURE_CODE = "workspace_validation_failed"; @@ -336,18 +330,17 @@ export function buildExecutionWorkspaceConfigSnapshot( return hasSnapshot ? snapshot : null; } -export function stripHostWorkspaceProvisionForLowTrustSandbox(input: { +export function stripHostWorkspaceProvision(input: { config: Record; - trustPreset: TrustPresetResolution; - selectedEnvironmentDriver: string | null | undefined; + skipHostProvision: boolean; }): Record { - if (input.trustPreset.kind !== "low_trust_review") return input.config; - if (input.selectedEnvironmentDriver !== "sandbox") return input.config; + if (!input.skipHostProvision) return input.config; const workspaceStrategy = parseObject(input.config.workspaceStrategy); if ( typeof workspaceStrategy.provisionCommand !== "string" && - typeof workspaceStrategy.runtimeProvisionCommand !== "string" + typeof workspaceStrategy.runtimeProvisionCommand !== "string" && + typeof input.config.runtimeProvisionCommand !== "string" ) return input.config; @@ -355,10 +348,9 @@ export function stripHostWorkspaceProvisionForLowTrustSandbox(input: { delete nextWorkspaceStrategy.provisionCommand; delete nextWorkspaceStrategy.runtimeProvisionCommand; - return { - ...input.config, - workspaceStrategy: nextWorkspaceStrategy, - }; + const nextConfig: Record = { ...input.config, workspaceStrategy: nextWorkspaceStrategy }; + delete nextConfig.runtimeProvisionCommand; + return nextConfig; } export async function preflightLowTrustWorkspaceIsolation(input: { @@ -448,6 +440,8 @@ function deriveRepoNameFromRepoUrl(repoUrl: string | null): string | null { } } +type PrepareManagedLocalSource = (sourceCwd: string, targetCwd: string) => Promise<(targetCwd: string) => Promise>; + /** * In-flight managed-checkout materializations keyed by target cwd. Two issues on the same * project can wake within seconds of each other; without this, both runs raced the same @@ -463,6 +457,7 @@ export async function ensureManagedProjectWorkspace(input: { companyId: string; projectId: string; repoUrl: string | null; + prepareLocalSource?: PrepareManagedLocalSource; /** Optional git credential source for cloning private repos; null/absent preserves ambient behavior. */ resolveGitAuth?: GitRemoteAuthProvider | null; }): Promise<{ cwd: string; warning: string | null }> { @@ -485,11 +480,19 @@ export async function ensureManagedProjectWorkspace(input: { // The winner may have cloned a different repository with the same basename. return ensureManagedProjectWorkspace(input); } - const attempt = materializeManagedProjectWorkspace(cwd, input).finally(() => { + const localSource = input.repoUrl ? localRepositoryPath(input.repoUrl) : null; + const guardLocalSource = localSource ? await input.prepareLocalSource?.(localSource, cwd) : undefined; + await guardLocalSource?.(cwd); + const attempt = materializeManagedProjectWorkspace(cwd, { + ...input, + beforeCopy: guardLocalSource, + beforePublish: guardLocalSource ? () => guardLocalSource(cwd) : undefined, + }).finally(() => { managedCheckoutMaterializations.delete(cwd); }); managedCheckoutMaterializations.set(cwd, attempt); const result = await attempt; + await guardLocalSource?.(cwd); if (input.repoUrl) { // A different server process can publish a same-name checkout between the // initial origin check and the atomic rename. Never adopt its other repo. @@ -503,139 +506,14 @@ export async function ensureManagedProjectWorkspace(input: { return result; } -async function materializeManagedProjectWorkspace( - cwd: string, - input: { - repoUrl: string | null; - repoRef?: string | null; - localSource?: string | null; - resolveGitAuth?: GitRemoteAuthProvider | null; - }, -): Promise<{ cwd: string; warning: string | null }> { - await fs.mkdir(path.dirname(cwd), { recursive: true }); - const stats = await fs.stat(cwd).catch(() => null); - - if (!input.repoUrl) { - if (!stats) { - await fs.mkdir(cwd, { recursive: true }); - } - return { cwd, warning: null }; - } - - const hasAdoptableGitDir = () => - fs - .stat(path.resolve(cwd, ".git")) - .then((entry) => entry.isDirectory()) - .catch(() => false); - if (await hasAdoptableGitDir()) { - return { cwd, warning: null }; - } - - if (stats) { - const entries = await fs.readdir(cwd).catch(() => []); - if (entries.length > 0) { - return { - cwd, - warning: `Managed workspace path "${cwd}" already exists but is not a git checkout. Using it as-is.`, - }; - } - await fs.rm(cwd, { recursive: true, force: true }); - } - - // Clone into a temp sibling, then move into place atomically. The shared target directory - // is never created in a partial state and never removed on failure, so a concurrent - // materialization (another process, or a run racing this one) can neither adopt a broken - // checkout nor lose its own completed one. - const auth = input.resolveGitAuth && !input.localSource - ? await input.resolveGitAuth(input.repoUrl) - : null; - const cloneTmpDir = await fs.mkdtemp(`${cwd}.clone-`); - try { - try { - await execFile( - "git", - [...(auth?.configArgs ?? []), "clone", "--no-hardlinks", "--", input.localSource ?? input.repoUrl, cloneTmpDir], - { - env: { - // Spread order matters: the sanitizer strips PAPERCLIP_*, which would remove the - // credential-helper token env if it came first. GIT_TERMINAL_PROMPT=0 fails a - // credential-less private clone immediately instead of hanging on a prompt until - // the clone timeout. - ...sanitizeRuntimeServiceBaseEnv(process.env), - GIT_TERMINAL_PROMPT: "0", - ...(auth?.env ?? {}), - }, - timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS, - }, - ); - } catch (error) { - const connectionFailure = !input.localSource ? classifyGitCloneFailure(input.repoUrl, error) : null; - if (connectionFailure) throw new GitConnectionFailureError( - error instanceof Error ? error.message : "Git clone failed", connectionFailure, - ); - throw error; - } - if (input.localSource) { - const snapshot = await readGitWorkspaceSnapshot(input.localSource, false); - if (!snapshot) throw new Error("Configured repository folder is not a Git checkout"); - let baseline; - try { - baseline = await captureDirectorySnapshot(cloneTmpDir, { exclude: [".git", ".paperclip-runtime", PROJECT_REPOSITORIES_DIR], ignoredPaths: snapshot.ignoredPaths, diskBacked: true }); - await mergeDirectoryWithBaseline({ baseline, sourceDir: input.localSource, targetDir: cloneTmpDir }); - } finally { - if (baseline) await disposeDirectorySnapshot(baseline); - await disposeGitWorkspaceSnapshot(snapshot); - } - await execFile("git", ["-C", cloneTmpDir, "remote", "set-url", "origin", input.repoUrl], { timeout: 10_000 }); - } else if (input.repoRef) { - await execFile("git", ["-C", cloneTmpDir, "checkout", input.repoRef], { timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS }); - } - } catch (error) { - await fs - .rm(cloneTmpDir, { recursive: true, force: true }) - .catch(() => undefined); - const reason = error instanceof Error ? error.message : String(error); - const authNote = describeGitAuthFailure({ - error: reason, - used: auth ? { source: auth.source, secretName: auth.secretName } : null, - }); - const message = scrubGitCredentialText( - `Failed to prepare managed checkout for "${input.repoUrl}" at "${cwd}": ${reason}${authNote ? ` ${authNote}` : ""}`, - ); - // Preserve the closed failure code without copying subprocess output or - // credentials into the durable run. Setup recovery needs the actual cause. - if (isWorkspaceGitScanError(error)) throw new WorkspaceGitScanError(error.code, message); - const connectionFailure = readGitConnectionFailure(error); - if (connectionFailure) throw new GitConnectionFailureError(message, connectionFailure); - throw new Error(message); - } - - try { - await fs.rename(cloneTmpDir, cwd); - } catch (renameError) { - await fs - .rm(cloneTmpDir, { recursive: true, force: true }) - .catch(() => undefined); - // The target appearing between the emptiness check and the rename means another - // materialization won the race; adopt its checkout instead of failing the run. - if (await hasAdoptableGitDir()) { - return { cwd, warning: null }; - } - const reason = - renameError instanceof Error ? renameError.message : String(renameError); - throw new Error( - `Failed to move managed checkout into place at "${cwd}": ${reason}`, - ); - } - return { cwd, warning: null }; -} - /** Keep every distinct project repository inside the task's writable/synced root. */ export async function prepareProjectRepositoryWorkspaces(input: { cwd: string; anchorRepoUrl: string | null; workspaces: Array & { cwd?: string | null }>; resolveGitAuth?: GitRemoteAuthProvider | null; + beforePublish?: () => Promise; + prepareLocalSource?: (sourceCwd: string) => Promise<() => Promise>; }): Promise> { const identity = (url: string) => url.trim().replace(/\.git\/?$/, "").replace(/\/$/, ""); const seen = new Set(input.anchorRepoUrl ? [identity(input.anchorRepoUrl)] : []); @@ -650,13 +528,7 @@ export async function prepareProjectRepositoryWorkspaces(input: { if (await fs.realpath(root) !== path.join(await fs.realpath(input.cwd), PROJECT_REPOSITORIES_DIR)) { throw new Error("Project repositories directory escapes the task workspace"); } - const excludePath = await execFile("git", ["-C", input.cwd, "rev-parse", "--git-path", "info/exclude"], { timeout: 10_000 }) - .then((result) => path.resolve(input.cwd, result.stdout.trim())); - const exclude = await fs.readFile(excludePath, "utf8").catch(() => ""); - if (!exclude.split(/\r?\n/).includes(`/${PROJECT_REPOSITORIES_DIR}/`)) { - await fs.mkdir(path.dirname(excludePath), { recursive: true }); - await fs.appendFile(excludePath, `\n/${PROJECT_REPOSITORIES_DIR}/\n`); - } + await ensureManagedRepositoriesIgnored(input.cwd); const results = []; for (const workspace of selected) { const repoUrl = workspace.repoUrl!; @@ -665,17 +537,27 @@ export async function prepareProjectRepositoryWorkspaces(input: { const cwd = path.join(root, key); const existing = await fs.lstat(cwd).catch(() => null); if (existing && (!existing.isDirectory() || existing.isSymbolicLink())) throw new Error("Invalid project repository checkout path"); - const localSource = workspace.cwd && workspace.cwd !== REPO_ONLY_CWD_SENTINEL - && await fs.stat(workspace.cwd).then((entry) => entry.isDirectory()).catch(() => false) - ? workspace.cwd : null; - const result = await materializeManagedProjectWorkspace(cwd, { repoUrl, repoRef: workspace.repoRef, localSource, resolveGitAuth: input.resolveGitAuth }); + const configuredLocalSource = workspace.cwd && workspace.cwd !== REPO_ONLY_CWD_SENTINEL ? workspace.cwd : null; + const localSource = configuredLocalSource + && await fs.stat(configuredLocalSource).then((entry) => entry.isDirectory()).catch(() => false) + ? configuredLocalSource : null; + const urlLocalSource = localRepositoryPath(repoUrl); + const sourceToAuthorize = localSource ?? urlLocalSource; + const recheckLocalSource = sourceToAuthorize + ? await input.prepareLocalSource?.(sourceToAuthorize) : undefined; + const beforePublish = async () => { + await recheckLocalSource?.(); + await input.beforePublish?.(); + }; + const result = await materializeManagedProjectWorkspace(cwd, { repoUrl, repoRef: workspace.repoRef, localSource, resolveGitAuth: input.resolveGitAuth, beforePublish }); if (result.warning) throw new Error(result.warning); + await beforePublish(); results.push({ workspaceId: workspace.id, cwd, repoUrl, repoRef: workspace.repoRef }); } // Retain detached checkout work outside the synchronized repository set. const active = new Set(results.map((repo) => path.basename(repo.cwd))); for (const entry of await fs.readdir(root)) { - if (active.has(entry) || entry.includes(".clone-")) continue; + if (active.has(entry) || entry.startsWith("task-repo-") || entry.includes(".clone-")) continue; const retained = path.join(input.cwd, ".paperclip-runtime", "detached-repositories", randomUUID()); await fs.mkdir(path.dirname(retained), { recursive: true }); await fs.rename(path.join(root, entry), retained); @@ -695,6 +577,7 @@ async function resolveConfiguredOrManagedProjectCwd(input: { cwd: string | null; repoUrl: string | null; resolveGitAuth?: GitRemoteAuthProvider | null; + prepareLocalSource?: PrepareManagedLocalSource; }): Promise<{ cwd: string; warning: string | null }> { const configuredCwd = readNonEmptyString(input.cwd); if (configuredCwd && configuredCwd !== REPO_ONLY_CWD_SENTINEL) { @@ -705,6 +588,7 @@ async function resolveConfiguredOrManagedProjectCwd(input: { projectId: input.projectId, repoUrl: readNonEmptyString(input.repoUrl), resolveGitAuth: input.resolveGitAuth ?? null, + prepareLocalSource: input.prepareLocalSource, }); } @@ -727,6 +611,7 @@ export interface ResolveAdditionalProjectWorkspaceDeps { function defaultAdditionalProjectWorkspaceDeps( db: Db, resolveGitAuth?: GitRemoteAuthProvider, + prepareLocalSource?: PrepareManagedLocalSource, ): ResolveAdditionalProjectWorkspaceDeps { return { loadProjectWorkspaceRows: (companyId, projectId) => @@ -743,6 +628,7 @@ function defaultAdditionalProjectWorkspaceDeps( resolveConfiguredOrManagedProjectCwd: (input) => resolveConfiguredOrManagedProjectCwd({ ...input, + prepareLocalSource: input.prepareLocalSource ?? prepareLocalSource, resolveGitAuth: input.resolveGitAuth ?? resolveGitAuth ?? @@ -751,6 +637,7 @@ function defaultAdditionalProjectWorkspaceDeps( ensureManagedProjectWorkspace: (input) => ensureManagedProjectWorkspace({ ...input, + prepareLocalSource: input.prepareLocalSource ?? prepareLocalSource, resolveGitAuth: input.resolveGitAuth ?? resolveGitAuth ?? @@ -1282,7 +1169,7 @@ export async function assertGitSensitiveAdapterWorkspaceValid(input: { }); }; - if (issue.projectWorkspaceId && !issue.projectId) { + if (issue.projectWorkspaceId && !issue.projectId && !input.persistedExecutionWorkspace?.projectId) { fail( "missing_project_id", `Issue ${issue.identifier ?? issue.id} is linked to a project workspace but has no project id; refusing to launch ${input.adapterType} from fallback cwd.`, @@ -1371,6 +1258,7 @@ export async function assertGitSensitiveAdapterWorkspaceValid(input: { if ( workspaceExpectation && + !(input.resolvedWorkspace.localPathOnlyWorkspace === true && input.executionWorkspace.strategy === "project_primary" && !input.executionWorkspace.repoUrl && !input.executionWorkspace.branchName) && effectiveCwd && !(await hasGitMetadata(effectiveCwd)) ) { @@ -2910,6 +2798,9 @@ function buildSessionConfigCategoryValues(input: { // boundary; the reusable row and its evolving generation are state. delete workspaceConfig.existingExecutionWorkspace; delete workspaceConfig.reusableExecutionWorkspaceConfig; + const issueSettings = { ...parseObject(workspaceConfig.issueSettings) }; + if (issueSettings.mode === workspaceConfig.requestedMode) delete issueSettings.mode; + workspaceConfig.issueSettings = Object.keys(issueSettings).length ? issueSettings : null; return { adapter: { adapterType: input.adapterType, @@ -3372,6 +3263,50 @@ function readNonEmptyString(value: unknown): string | null { /** Database-bound workspace resolution for a heartbeat service instance. */ export function createHeartbeatWorkspaceResolver(db: Db) { + function managedLocalSourceGuard( + agent: typeof agents.$inferSelect, + issueId: string | null, + runId: string | null, + ): PrepareManagedLocalSource { + return async (sourceCwd) => { + const [run] = runId + ? await db.select().from(heartbeatRuns).where(and( + eq(heartbeatRuns.id, runId), + eq(heartbeatRuns.companyId, agent.companyId), + eq(heartbeatRuns.agentId, agent.id), + )) + : []; + const runIssueId = run?.nativeIssueId ?? run?.contextSnapshot?.issueId + ?? run?.contextSnapshot?.taskId ?? run?.issueId ?? null; + if (!run || runIssueId !== issueId) { + throw forbidden("Local repository source requires an authoritative run identity"); + } + const actor = { + type: "agent" as const, + source: "agent_jwt" as const, + companyId: agent.companyId, + agentId: agent.id, + runId: run.id, + onBehalfOfUserId: run.responsibleUserId === "local-board" ? null : run.responsibleUserId, + }; + const captured = await assertIsolatedWorkspaceSourceReadAccess(db, { + companyId: agent.companyId, + sourceCwd, + actor, + copyKind: "repository", + }); + return targetCwd => retainIsolatedWorkspaceSourceAccess(db, { + companyId: agent.companyId, + sourceCwd, + targetCwd, + actor, + captured, + issueId: issueId ?? undefined, + copyKind: "repository", + }); + }; + } + async function resolveReusedGitWorkspaceAnchor(input: { agent: typeof agents.$inferSelect; workspace: ExecutionWorkspace; @@ -3390,15 +3325,15 @@ export function createHeartbeatWorkspaceResolver(db: Db) { ? await db.select({ projectId: issues.projectId, projectWorkspaceId: issues.projectWorkspaceId }).from(issues) .where(and(eq(issues.id, input.issueId), eq(issues.companyId, agent.companyId))).then(rows => rows[0] ?? null) : null; - const sourceProjectId = input.issueId && !input.immutableNativeBinding ? currentIssueSource?.projectId ?? null : input.projectId; + const sourceProjectId = workspace.projectId; const explicitProjectWorkspaceId = currentIssueSource?.projectWorkspaceId ?? input.explicitProjectWorkspaceId; const projectWorkspaceRows = await db.select().from(projectWorkspaces).where(and( eq(projectWorkspaces.companyId, agent.companyId), - eq(projectWorkspaces.projectId, workspace.projectId), + workspace.projectId ? eq(projectWorkspaces.projectId, workspace.projectId) : sql`false`, )).orderBy(asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id)); const boundProjectWorkspace = projectWorkspaceRows.find(row => row.id === workspace.projectWorkspaceId) ?? null; const configuredSource = prioritizeProjectWorkspaceCandidatesForRun(projectWorkspaceRows, explicitProjectWorkspaceId)[0]; - const managedBase = workspace.repoUrl ? resolveManagedProjectWorkspaceDir({ + const managedBase = workspace.repoUrl && workspace.projectId ? resolveManagedProjectWorkspaceDir({ companyId: agent.companyId, projectId: workspace.projectId, repoName: deriveRepoNameFromRepoUrl(workspace.repoUrl), @@ -3416,11 +3351,12 @@ export function createHeartbeatWorkspaceResolver(db: Db) { boundProjectWorkspace, candidateBaseCwds, managedSourceRoot: resolvePaperclipInstanceRoot(), - materializeOriginalRepository: workspace.repoUrl ? async () => { + materializeOriginalRepository: workspace.repoUrl && workspace.projectId ? async () => { const original = await ensureManagedProjectWorkspace({ companyId: agent.companyId, - projectId: workspace.projectId, + projectId: workspace.projectId!, repoUrl: workspace.repoUrl, + prepareLocalSource: managedLocalSourceGuard(agent, input.issueId, input.runId), resolveGitAuth: createGitRemoteAuthProvider(db, agent.companyId, { issueId: input.issueId, heartbeatRunId: input.runId, agentId: agent.id, responsibleUserId: input.responsibleUserId, @@ -3455,7 +3391,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) { agent: typeof agents.$inferSelect, context: Record, previousSessionParams: Record | null, - opts?: { useProjectWorkspace?: boolean | null }, + opts?: { useProjectWorkspace?: boolean | null; configuredCwd?: string | null }, ): Promise { const issueId = readNonEmptyString(context.issueId) ?? readNonEmptyString(context.taskId); @@ -3476,6 +3412,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) { .select({ projectId: issues.projectId, projectWorkspaceId: issues.projectWorkspaceId, + workspaceSelection: issues.workspaceSelection, }) .from(issues) .where( @@ -3484,11 +3421,17 @@ export function createHeartbeatWorkspaceResolver(db: Db) { .then((rows) => rows[0] ?? null) : null; const issueProjectId = issueProjectRef?.projectId ?? null; + const workspaceSelection = issueProjectRef?.workspaceSelection?.selection; + const configuredSourceId = workspaceSelection?.kind === "configured_source" + ? workspaceSelection.projectWorkspaceId : null; const preferredProjectWorkspaceId = - issueProjectRef?.projectWorkspaceId ?? contextProjectWorkspaceId ?? null; - const resolvedProjectId = issueProjectId ?? contextProjectId; + configuredSourceId ?? issueProjectRef?.projectWorkspaceId ?? contextProjectWorkspaceId ?? null; + const [selectedSource] = preferredProjectWorkspaceId ? await db.select({ projectId: projectWorkspaces.projectId }).from(projectWorkspaces) + .where(and(eq(projectWorkspaces.id, preferredProjectWorkspaceId), eq(projectWorkspaces.companyId, agent.companyId))) : []; + const resolvedProjectId = selectedSource?.projectId ?? issueProjectId ?? contextProjectId; + const forceTaskDirectory = workspaceSelection?.kind === "task_directory"; const useProjectWorkspace = opts?.useProjectWorkspace !== false; - const workspaceProjectId = useProjectWorkspace ? resolvedProjectId : null; + const workspaceProjectId = useProjectWorkspace && !forceTaskDirectory ? resolvedProjectId : null; const unorderedProjectWorkspaceRows = workspaceProjectId ? await db @@ -3502,10 +3445,13 @@ export function createHeartbeatWorkspaceResolver(db: Db) { ) .orderBy(asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id)) : []; - const projectWorkspaceRows = prioritizeProjectWorkspaceCandidatesForRun( + const prioritizedWorkspaceRows = prioritizeProjectWorkspaceCandidatesForRun( unorderedProjectWorkspaceRows, preferredProjectWorkspaceId, ); + const projectWorkspaceRows = configuredSourceId + ? prioritizedWorkspaceRows.filter((workspace) => workspace.id === configuredSourceId) + : prioritizedWorkspaceRows; const workspaceHints = projectWorkspaceRows.map((workspace) => ({ workspaceId: workspace.id, @@ -3514,7 +3460,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) { repoRef: readNonEmptyString(workspace.repoRef), })); - if (projectWorkspaceRows.length > 0) { + if (projectWorkspaceRows.length > 0 || configuredSourceId) { const preferredWorkspace = preferredProjectWorkspaceId ? (projectWorkspaceRows.find( (workspace) => workspace.id === preferredProjectWorkspaceId, @@ -3538,6 +3484,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) { cwd: workspace.cwd, repoUrl: workspace.repoUrl, resolveGitAuth, + prepareLocalSource: managedLocalSourceGuard(agent, issueId, readNonEmptyString(context.executionIdentityRunId)), }); projectCwd = resolvedCwd.cwd; managedWorkspaceWarning = resolvedCwd.warning; @@ -3592,52 +3539,31 @@ export function createHeartbeatWorkspaceResolver(db: Db) { missingProjectCwds.push(projectCwd); } - const fallbackCwd = resolveDefaultAgentWorkspaceDir(agent.id); - await fs.mkdir(fallbackCwd, { recursive: true }); - const warnings = buildAnchorFallbackWorkspaceNotes({ - fallbackCwd, - preferredWorkspaceWarning, - materializationFailures, - missingProjectCwds, - hasConfiguredProjectCwd, - }); - return { - cwd: fallbackCwd, - source: "project_primary" as const, - projectId: resolvedProjectId, - workspaceId: projectWorkspaceRows[0]?.id ?? null, - repoUrl: projectWorkspaceRows[0]?.repoUrl ?? null, - repoRef: projectWorkspaceRows[0]?.repoRef ?? null, - workspaceHints, - warnings, - baseCwdFallback: true, - materializationFailures, - }; - } - - if (workspaceProjectId) { - const managedWorkspace = await ensureManagedProjectWorkspace({ - companyId: agent.companyId, - projectId: workspaceProjectId, - repoUrl: null, - }); - return { - cwd: managedWorkspace.cwd, - source: "project_primary" as const, - projectId: resolvedProjectId, - workspaceId: null, - repoUrl: null, - repoRef: null, - workspaceHints, - warnings: managedWorkspace.warning ? [managedWorkspace.warning] : [], - baseCwdFallback: false, - materializationFailures: [], - }; + const connections = materializationFailures.map((failure) => readConnectionFailure(failure.connectionFailure)); + const connectionFailure = connections.length > 0 && connections.every((failure) => failure?.provider === "git") + ? connections[0] as GitConnectionFailure : undefined; + throw new WorkspaceValidationFailure( + `Configured project workspace is unavailable: ${preferredWorkspaceWarning ?? materializationFailures[0]?.error ?? missingProjectCwds[0] ?? "source could not be prepared"}. Repair the configured path or repository access, then retry.`, + { + ...(connectionFailure ? { connectionFailure } : {}), + workspaceValidation: { + reason: materializationFailures.length > 0 ? "git_worktree_base_materialization_failed" : "configured_workspace_unavailable", + issueId, + issueProjectId, + issueProjectWorkspaceId: preferredProjectWorkspaceId, + resolvedWorkspaceSource: "project_primary", + resolvedProjectId, + resolvedProjectWorkspaceId: preferredWorkspace?.id ?? null, + baseCwdFallback: false, + materializationFailures, + }, + }, + ); } const sessionCwd = readNonEmptyString(previousSessionParams?.cwd); const sessionCwdLooksUnsafe = isUnsafeSessionWorkspaceCwd(sessionCwd); - if (sessionCwd && !sessionCwdLooksUnsafe) { + if (!forceTaskDirectory && sessionCwd && !sessionCwdLooksUnsafe) { const sessionCwdExists = await fs .stat(sessionCwd) .then((stats) => stats.isDirectory()) @@ -3658,7 +3584,12 @@ export function createHeartbeatWorkspaceResolver(db: Db) { } } - const cwd = resolveDefaultAgentWorkspaceDir(agent.id); + const configuredCwd = forceTaskDirectory ? null + : opts?.configuredCwd !== undefined ? opts.configuredCwd + : readNonEmptyString(parseObject(agent.adapterConfig).cwd); + const cwd = configuredCwd ? path.resolve(configuredCwd) + : issueId ? await materializeIsolatedTaskDirectory({ companyId: agent.companyId, issueId }) + : resolveDefaultAgentWorkspaceDir(agent.id); await fs.mkdir(cwd, { recursive: true }); const warnings: string[] = []; if (sessionCwd && sessionCwdLooksUnsafe) { @@ -3680,8 +3611,8 @@ export function createHeartbeatWorkspaceResolver(db: Db) { } return { cwd, - source: "agent_home" as const, - projectId: resolvedProjectId, + source: issueId || configuredCwd ? "task_session" as const : "agent_home" as const, + projectId: null, workspaceId: null, repoUrl: null, repoRef: null, @@ -3703,6 +3634,7 @@ export function createHeartbeatWorkspaceResolver(db: Db) { previousSessionParams: Record | null, opts?: { useProjectWorkspace?: boolean | null; + configuredCwd?: string | null; executionEnvironmentDriver?: string | null; anchorWorkspace?: ResolvedAnchorWorkspaceForRun; }, @@ -3728,12 +3660,12 @@ export function createHeartbeatWorkspaceResolver(db: Db) { const executionEnvironmentDriver = opts?.executionEnvironmentDriver ?? null; const issueId = readNonEmptyString(context.issueId) ?? readNonEmptyString(context.taskId); + const responsibleUserId = readNonEmptyString(context.responsibleUserId) + ?? readNonEmptyString(context.responsible_user_id); const resolveGitAuth = createGitRemoteAuthProvider(db, agent.companyId, { issueId, heartbeatRunId: readNonEmptyString(context.executionIdentityRunId), - responsibleUserId: - readNonEmptyString(context.responsibleUserId) ?? - readNonEmptyString(context.responsible_user_id), + responsibleUserId, agentId: agent.id, }); const { additionalWorkspaces, warnings, failures } = @@ -3752,6 +3684,8 @@ export function createHeartbeatWorkspaceResolver(db: Db) { agentId: agent.id, companyId: agent.companyId, source: "agent_key", + runId: readNonEmptyString(context.executionIdentityRunId), + onBehalfOfUserId: responsibleUserId === "local-board" ? null : responsibleUserId, }, issues: issueService(db), projects: projectService(db), @@ -3759,7 +3693,8 @@ export function createHeartbeatWorkspaceResolver(db: Db) { resolveProjectWorkspace: (project) => resolveAdditionalProjectWorkspace( { companyId: agent.companyId, project }, - defaultAdditionalProjectWorkspaceDeps(db, resolveGitAuth), + defaultAdditionalProjectWorkspaceDeps(db, resolveGitAuth, + managedLocalSourceGuard(agent, issueId, readNonEmptyString(context.executionIdentityRunId))), ), }); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 70af5005448..35a606cf7d5 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -1,3 +1,8 @@ +import { resolveProjectDefaultWorkspaceId } from "./project-default-workspace.js"; +import { type AuthorizationActor, executionWorkspaceReadSqlCondition, projectReadSqlCondition } from "./authorization.js"; +import { assertTaskWorkspaceAccess, assertTaskWorkspaceSourceProjectAccess } from "./task-workspace-source-access.js"; +import { type TaskWorkspaceSelection, type TaskWorkspaceIntent } from "@paperclipai/shared"; +import { executionWorkspaceService } from "./execution-workspaces.js"; import { notifyChatPublicationWork } from "./chat-work-notifications.js"; import { recordChatHandoff, recordChatCompletion, existingChatCompletionReply, acknowledgeChatCompletionReply } from "./chat-completion-delivery.js"; import { mirrorSlackBoardComment, slackBoardReplyBindings } from "./slack-board-messages.js"; @@ -1938,7 +1943,10 @@ async function assertExecutionTaskParent(db: Db, companyId: string, parentId?: s } type DbTransaction = Parameters[0]>[0]; -type IssueCreateInput = Omit & { +type IssueCreateInput = Omit & { + workspaceSelection?: TaskWorkspaceSelection; + workspaceSelectionSource?: "explicit" | "channel"; + workspaceSelectionActor?: AuthorizationActor; title?: string; initialPlan?: string | null; labelIds?: string[]; @@ -5000,6 +5008,9 @@ const issueListSelect = { executionWorkspaceId: issues.executionWorkspaceId, executionWorkspacePreference: issues.executionWorkspacePreference, executionWorkspaceSettings: sql`null`, + workspaceSelection: sql`null`, + workspacePendingSelection: sql`null`, + workspaceBindingRevision: issues.workspaceBindingRevision, sourceTrust: issues.sourceTrust, unblockDescriptor: issues.unblockDescriptor, blockedTransitionAt: issues.blockedTransitionAt, @@ -7295,6 +7306,7 @@ export function issueService(db: Db) { id: executionWorkspaces.id, companyId: executionWorkspaces.companyId, projectId: executionWorkspaces.projectId, + projectWorkspaceId: executionWorkspaces.projectWorkspaceId, }) .from(executionWorkspaces) .where(eq(executionWorkspaces.id, executionWorkspaceId)) @@ -7302,11 +7314,6 @@ export function issueService(db: Db) { if (!workspace) throw notFound("Execution workspace not found"); if (workspace.companyId !== companyId) throw unprocessable("Execution workspace must belong to same company"); - if (projectId && workspace.projectId !== projectId) { - throw unprocessable( - "Execution workspace must belong to the selected project", - ); - } return workspace; } @@ -9469,6 +9476,7 @@ export function issueService(db: Db) { const childProjectId = issueData.projectId ?? parent.projectId; const childInheritsParentProject = childProjectId === parent.projectId; const hasExplicitExecutionWorkspaceOverride = + issueData.projectWorkspaceId !== undefined || issueData.executionWorkspaceId !== undefined || issueData.executionWorkspacePreference !== undefined || issueData.executionWorkspaceSettings !== undefined; @@ -9483,10 +9491,11 @@ export function issueService(db: Db) { parentId: parent.id, projectId: childProjectId, projectWorkspaceId: - issueData.projectWorkspaceId ?? - (inheritStrategyOnly && childInheritsParentProject - ? parent.projectWorkspaceId - : undefined), + issueData.projectWorkspaceId !== undefined + ? issueData.projectWorkspaceId + : (inheritStrategyOnly && childInheritsParentProject + ? parent.projectWorkspaceId + : undefined), goalId: issueData.goalId ?? parent.goalId, actorResponsibleUserId: issueData.actorResponsibleUserId ?? null, trustExplicitResponsibleUserId: @@ -9914,6 +9923,7 @@ export function issueService(db: Db) { dbOrTx: Db | DbTransaction = db, ) => { const { + workspaceSelection, workspaceSelectionSource = "explicit", workspaceSelectionActor, initialPlan, labelIds: inputLabelIds, blockedByIssueIds, @@ -10081,12 +10091,15 @@ export function issueService(db: Db) { let executionWorkspaceSettings = (issueData.executionWorkspaceSettings as Record | null | undefined) ?? null; + let inheritedWorkspaceSelection = false; const workspaceInheritanceIssueId = skipExecutionWorkspaceInheritance ? null : (inheritExecutionWorkspaceFromIssueId ?? issueData.parentId ?? null); const hasExplicitExecutionWorkspaceOverride = + (workspaceSelection !== undefined && workspaceSelectionSource === "explicit") || + issueData.projectWorkspaceId !== undefined || issueData.executionWorkspaceId !== undefined || issueData.executionWorkspacePreference !== undefined || issueData.executionWorkspaceSettings !== undefined; @@ -10110,14 +10123,15 @@ export function issueService(db: Db) { issueData.projectId === workspaceSource.projectId; if ( inheritsSourceProject && + issueData.projectWorkspaceId === undefined && projectWorkspaceId == null && workspaceSource.projectWorkspaceId ) { projectWorkspaceId = workspaceSource.projectWorkspaceId; + inheritedWorkspaceSelection = true; } if ( inheritsSourceProject && - isolatedWorkspacesEnabled && !hasExplicitExecutionWorkspaceOverride && workspaceSource.executionWorkspaceId ) { @@ -10136,6 +10150,7 @@ export function issueService(db: Db) { .then((rows) => rows[0] ?? null); if (sourceWorkspace) { executionWorkspaceId = sourceWorkspace.id; + inheritedWorkspaceSelection = true; executionWorkspacePreference = "reuse_existing"; executionWorkspaceSettings = { ...((workspaceSource.executionWorkspaceSettings as @@ -10147,23 +10162,26 @@ export function issueService(db: Db) { } } } - if (issueData.projectId == null && projectWorkspaceId) { - const workspace = await assertValidProjectWorkspace( - companyId, - null, - projectWorkspaceId, - tx, - ); - issueData.projectId = workspace.projectId; + const workspaceActor: AuthorizationActor = workspaceSelectionActor ?? (issueData.createdByUserId + ? { type: "board", userId: issueData.createdByUserId, companyIds: [companyId] } + : { type: "agent", agentId: issueData.createdByAgentId ?? issueData.assigneeAgentId, companyId }); + let workspaceIntent: TaskWorkspaceIntent | null = null; + if (workspaceSelection && (workspaceSelectionSource === "explicit" || !inheritedWorkspaceSelection)) { + const projection = await executionWorkspaceService(db).validateSelection({ companyId, + actor: workspaceActor, + selection: workspaceSelection, parentIssueId: issueData.parentId, + taskCreation: { parentIssueId: issueData.parentId, projectId: issueData.projectId, + assigneeAgentId: issueData.assigneeAgentId, executionPolicy: issueData.executionPolicy }, + assigneeAgentId: issueData.assigneeAgentId, assigneeUserId: issueData.assigneeUserId, assigneeAdapterOverrides: issueData.assigneeAdapterOverrides, projectId: issueData.projectId, executionPolicy: issueData.executionPolicy }, tx); + projectWorkspaceId = projection.projectWorkspaceId; + executionWorkspaceId = projection.executionWorkspaceId; + executionWorkspacePreference = projection.executionWorkspacePreference; + executionWorkspaceSettings = projection.executionWorkspaceSettings; + workspaceIntent = { version: 1, selection: workspaceSelection, source: workspaceSelectionSource }; } - if (issueData.projectId == null && executionWorkspaceId) { - const workspace = await assertValidExecutionWorkspace( - companyId, - null, - executionWorkspaceId, - tx, - ); - issueData.projectId = workspace.projectId; + if (executionWorkspaceId) { + const workspace = await assertValidExecutionWorkspace(companyId, null, executionWorkspaceId, tx); + projectWorkspaceId = workspace.projectWorkspaceId; } const projectGoalId = await getProjectDefaultGoalId( tx, @@ -10211,7 +10229,7 @@ export function issueService(db: Db) { ), ) as Record | null; } - if (!projectWorkspaceId && issueData.projectId) { + if (!executionWorkspaceId && !projectWorkspaceId && issueData.projectId && workspaceIntent?.selection.kind !== "task_directory") { const project = await tx .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy, @@ -10227,41 +10245,38 @@ export function issueService(db: Db) { const projectPolicy = parseProjectExecutionWorkspacePolicy( project?.executionWorkspacePolicy, ); - projectWorkspaceId = projectPolicy?.defaultProjectWorkspaceId ?? null; - if (!projectWorkspaceId) { - projectWorkspaceId = await tx - .select({ id: projectWorkspaces.id }) - .from(projectWorkspaces) - .where( - and( - eq(projectWorkspaces.projectId, issueData.projectId), - eq(projectWorkspaces.companyId, companyId), - ), - ) - .orderBy( - desc(projectWorkspaces.isPrimary), - asc(projectWorkspaces.createdAt), - asc(projectWorkspaces.id), - ) - .then((rows) => rows[0]?.id ?? null); - } + projectWorkspaceId = await resolveProjectDefaultWorkspaceId(tx, { + companyId, projectId: issueData.projectId, policy: projectPolicy, + }); + } + if (projectWorkspaceId && workspaceSelectionActor) { + const [readableSource] = await tx.select({ id: projectWorkspaces.id, projectId: projectWorkspaces.projectId, executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projectWorkspaces) + .innerJoin(projects, eq(projects.id, projectWorkspaces.projectId)) + .where(and(eq(projectWorkspaces.id, projectWorkspaceId), eq(projectWorkspaces.companyId, companyId), await projectReadSqlCondition(tx, workspaceActor))); + if (!readableSource) throw notFound("Workspace source is unavailable or inaccessible"); + const sourceStrategy = await executionWorkspaceService(db).resolveSourceWorkspaceStrategy({ companyId, + assigneeAgentId: issueData.assigneeAgentId, assigneeAdapterOverrides: issueData.assigneeAdapterOverrides, + executionWorkspaceSettings, executionWorkspacePolicy: readableSource.executionWorkspacePolicy, + typedSelection: Boolean(workspaceIntent), projectId: issueData.projectId, executionPolicy: issueData.executionPolicy }, tx); + await assertTaskWorkspaceSourceProjectAccess(tx, workspaceActor, companyId, readableSource.projectId, { + parentIssueId: issueData.parentId, assigneeAgentId: issueData.assigneeAgentId, assigneeUserId: issueData.assigneeUserId, + write: !executionWorkspaceId && sourceStrategy !== "git_worktree", + }); + } + if (executionWorkspaceId && workspaceSelectionActor) { + const [readableWorkspace] = await tx.select({ id: executionWorkspaces.id, projectId: executionWorkspaces.projectId }).from(executionWorkspaces) + .where(and(eq(executionWorkspaces.id, executionWorkspaceId), eq(executionWorkspaces.companyId, companyId), await executionWorkspaceReadSqlCondition(tx, workspaceActor))); + if (!readableWorkspace) throw notFound("Workspace is unavailable or inaccessible"); + await assertTaskWorkspaceAccess(tx, workspaceActor, companyId, readableWorkspace.id, { write: true, parentIssueId: issueData.parentId, assigneeAgentId: issueData.assigneeAgentId, assigneeUserId: issueData.assigneeUserId }); } if (projectWorkspaceId) { await assertValidProjectWorkspace( companyId, - issueData.projectId, + null, projectWorkspaceId, tx, ); } - if (executionWorkspaceId) { - await assertValidExecutionWorkspace( - companyId, - issueData.projectId, - executionWorkspaceId, - tx, - ); - } if ( isolatedWorkspacesEnabled && issueData.executionWorkspaceSettings !== undefined @@ -10368,6 +10383,7 @@ export function issueService(db: Db) { const values = { ...issueData, + workspaceSelection: workspaceIntent, visibility, privacyRootIssueId, privacyParentIssueId, @@ -10382,12 +10398,10 @@ export function issueService(db: Db) { projectGoalId, defaultGoalId: defaultCompanyGoal?.id ?? null, }), - ...(projectWorkspaceId ? { projectWorkspaceId } : {}), - ...(executionWorkspaceId ? { executionWorkspaceId } : {}), - ...(executionWorkspacePreference - ? { executionWorkspacePreference } - : {}), - ...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}), + projectWorkspaceId, + executionWorkspaceId, + executionWorkspacePreference, + executionWorkspaceSettings, companyId, issueNumber, identifier, @@ -10565,25 +10579,9 @@ export function issueService(db: Db) { const policy = parseProjectExecutionWorkspacePolicy( projectRow?.executionWorkspacePolicy, ); - let defaultProjectWorkspaceId = - policy?.defaultProjectWorkspaceId ?? null; - if (!defaultProjectWorkspaceId) { - defaultProjectWorkspaceId = await tx - .select({ id: projectWorkspaces.id }) - .from(projectWorkspaces) - .where( - and( - eq(projectWorkspaces.projectId, projectId), - eq(projectWorkspaces.companyId, companyId), - ), - ) - .orderBy( - desc(projectWorkspaces.isPrimary), - asc(projectWorkspaces.createdAt), - asc(projectWorkspaces.id), - ) - .then((r) => r[0]?.id ?? null); - } + const defaultProjectWorkspaceId = await resolveProjectDefaultWorkspaceId(tx, { + companyId, projectId, policy, + }); const defaultExecutionWorkspaceSettings = defaultIssueExecutionWorkspaceSettingsForProject( gateProjectExecutionWorkspacePolicy( @@ -10835,6 +10833,7 @@ export function issueService(db: Db) { actorRunStopId?: string | null; actorUserId?: string | null; companyGuard?: string; + workspaceSelectionActor?: AuthorizationActor; expectedExecutionPolicy?: typeof issues.$inferInsert.executionPolicy; }, dbOrTx: any = db, @@ -10883,6 +10882,7 @@ export function issueService(db: Db) { actorRunStopId, actorUserId, companyGuard, + workspaceSelectionActor, expectedExecutionPolicy, ...issueData } = data; @@ -11084,46 +11084,8 @@ export function issueService(db: Db) { ? { ...nextExecutionWorkspaceSettings } : null; } - let validatedProjectWorkspace: { projectId: string } | null = null; - let validatedExecutionWorkspace: { projectId: string } | null = null; - if (!nextProjectId && nextProjectWorkspaceId) { - const workspace = await assertValidProjectWorkspace( - existing.companyId, - null, - nextProjectWorkspaceId, - ); - validatedProjectWorkspace = workspace; - nextProjectId = workspace.projectId; - patch.projectId = workspace.projectId; - } - if (!nextProjectId && nextExecutionWorkspaceId) { - const workspace = await assertValidExecutionWorkspace( - existing.companyId, - null, - nextExecutionWorkspaceId, - ); - validatedExecutionWorkspace = workspace; - nextProjectId = workspace.projectId; - patch.projectId = workspace.projectId; - } - if (nextProjectWorkspaceId) { - if (!validatedProjectWorkspace) { - await assertValidProjectWorkspace( - existing.companyId, - nextProjectId, - nextProjectWorkspaceId, - ); - } - } - if (nextExecutionWorkspaceId) { - if (!validatedExecutionWorkspace) { - await assertValidExecutionWorkspace( - existing.companyId, - nextProjectId, - nextExecutionWorkspaceId, - ); - } - } + if (nextProjectWorkspaceId) await assertValidProjectWorkspace(existing.companyId, null, nextProjectWorkspaceId); + if (nextExecutionWorkspaceId) await assertValidExecutionWorkspace(existing.companyId, null, nextExecutionWorkspaceId); if ( isolatedWorkspacesEnabled && issueData.executionWorkspaceSettings !== undefined @@ -11176,6 +11138,12 @@ export function issueService(db: Db) { .for("update") .then((rows: Array) => rows[0] ?? null); if (!receiptExisting) return null; + if (issueData.executionWorkspaceId !== undefined || issueData.projectWorkspaceId !== undefined || + issueData.executionWorkspacePreference !== undefined || issueData.executionWorkspaceSettings !== undefined) { + Object.assign(patch, await executionWorkspaceService(db).prepareTaskWorkspaceUpdate({ + companyId: receiptExisting.companyId, issueId: receiptExisting.id, patch, + }, tx)); + } if (expectedExecutionPolicy !== undefined && !isDeepStrictEqual( receiptExisting.executionPolicy ?? null, expectedExecutionPolicy, @@ -11282,6 +11250,11 @@ export function issueService(db: Db) { .returning() .then((rows: Array) => rows[0] ?? null); if (!updated) return null; + if (workspaceSelectionActor && updated.workspaceBindingRevision !== receiptExisting.workspaceBindingRevision) { + await executionWorkspaceService(db).assertTaskWorkspaceUpdateAccess({ + task: updated, actor: workspaceSelectionActor, + }, tx); + } if (issueData.description !== undefined) await attachOwnedDraftImages(tx, updated, updated.description, { userId: actorUserId, agentId: actorAgentId }); if (changesPrivacy && updated.visibility === "private") { diff --git a/server/src/services/local-repository-path.ts b/server/src/services/local-repository-path.ts new file mode 100644 index 00000000000..90aeb37231a --- /dev/null +++ b/server/src/services/local-repository-path.ts @@ -0,0 +1,13 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +export function localRepositoryPath(repoUrl: string): string | null { + if (/^file:/i.test(repoUrl.trim())) { + const localUrl = new URL(repoUrl); + if (repoUrl !== repoUrl.trim() || localUrl.search || localUrl.hash) { + throw new Error("Local repository URLs must not contain surrounding whitespace, queries, or fragments"); + } + return fileURLToPath(localUrl); + } + return path.isAbsolute(repoUrl) ? repoUrl : null; +} diff --git a/server/src/services/managed-repository-checkout.ts b/server/src/services/managed-repository-checkout.ts new file mode 100644 index 00000000000..f8810406f06 --- /dev/null +++ b/server/src/services/managed-repository-checkout.ts @@ -0,0 +1,149 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { execFile as execFileCallback } from "node:child_process"; +import { promisify } from "node:util"; +import { PROJECT_REPOSITORIES_DIR, readGitWorkspaceSnapshot, disposeGitWorkspaceSnapshot } from "@paperclipai/adapter-utils/git-workspace-sync"; +import { captureDirectorySnapshot, disposeDirectorySnapshot, mergeDirectoryWithBaseline } from "@paperclipai/adapter-utils/workspace-restore-merge"; +import { sanitizeRuntimeServiceBaseEnv } from "./runtime-service-env.js"; +import { describeGitAuthFailure, scrubGitCredentialText, type GitRemoteAuthProvider } from "./git-credentials.js"; +import { classifyGitCloneFailure, GitConnectionFailureError, readGitConnectionFailure } from "./git-connection-failure.js"; +import { isWorkspaceGitScanError, WorkspaceGitScanError } from "./workspace-git-operation-scheduler.js"; + +const execFile = promisify(execFileCallback); +const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000; + +export async function materializeManagedProjectWorkspace( + cwd: string, + input: { + repoUrl: string | null; + repoRef?: string | null; + localSource?: string | null; + resolveGitAuth?: GitRemoteAuthProvider | null; + beforeCopy?: (cloneCwd: string) => Promise; + beforePublish?: (cloneCwd: string) => Promise; + stagingParent?: string; + }, +): Promise<{ cwd: string; warning: string | null }> { + await fs.mkdir(path.dirname(cwd), { recursive: true }); + const stats = await fs.stat(cwd).catch(() => null); + + if (!input.repoUrl) { + if (!stats) { + await fs.mkdir(cwd, { recursive: true }); + } + return { cwd, warning: null }; + } + + const hasAdoptableGitDir = () => + fs + .stat(path.resolve(cwd, ".git")) + .then((entry) => entry.isDirectory()) + .catch(() => false); + if (await hasAdoptableGitDir()) { + return { cwd, warning: null }; + } + + if (stats) { + const entries = await fs.readdir(cwd).catch(() => []); + if (entries.length > 0) { + return { + cwd, + warning: `Managed workspace path "${cwd}" already exists but is not a git checkout. Using it as-is.`, + }; + } + await fs.rm(cwd, { recursive: true, force: true }); + } + + const auth = input.resolveGitAuth && !input.localSource + ? await input.resolveGitAuth(input.repoUrl) + : null; + const cloneTmpDir = await fs.mkdtemp(input.stagingParent + ? path.join(input.stagingParent, `${path.basename(cwd)}.clone-`) + : `${cwd}.clone-`); + try { + await input.beforeCopy?.(cloneTmpDir); + try { + await execFile( + "git", + [...(auth?.configArgs ?? []), "clone", "--no-hardlinks", "--", input.localSource ?? input.repoUrl, cloneTmpDir], + { + env: { + ...sanitizeRuntimeServiceBaseEnv(process.env), + GIT_TERMINAL_PROMPT: "0", + ...(auth?.env ?? {}), + }, + timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS, + }, + ); + } catch (error) { + const connectionFailure = !input.localSource ? classifyGitCloneFailure(input.repoUrl, error) : null; + if (connectionFailure) throw new GitConnectionFailureError( + error instanceof Error ? error.message : "Git clone failed", connectionFailure, + ); + throw error; + } + if (input.localSource) { + const snapshot = await readGitWorkspaceSnapshot(input.localSource, false); + if (!snapshot) throw new Error("Configured repository folder is not a Git checkout"); + let baseline; + try { + baseline = await captureDirectorySnapshot(cloneTmpDir, { exclude: [".git", ".paperclip-runtime", PROJECT_REPOSITORIES_DIR], ignoredPaths: snapshot.ignoredPaths, diskBacked: true }); + await mergeDirectoryWithBaseline({ baseline, sourceDir: input.localSource, targetDir: cloneTmpDir }); + } finally { + if (baseline) await disposeDirectorySnapshot(baseline); + await disposeGitWorkspaceSnapshot(snapshot); + } + await execFile("git", ["-C", cloneTmpDir, "remote", "set-url", "origin", input.repoUrl], { timeout: 10_000 }); + } else if (input.repoRef) { + await execFile("git", ["-C", cloneTmpDir, "checkout", input.repoRef], { timeout: MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS }); + } + await input.beforePublish?.(cloneTmpDir); + } catch (error) { + await fs + .rm(cloneTmpDir, { recursive: true, force: true }) + .catch(() => undefined); + const reason = error instanceof Error ? error.message : String(error); + const authNote = describeGitAuthFailure({ + error: reason, + used: auth ? { source: auth.source, secretName: auth.secretName } : null, + }); + const message = scrubGitCredentialText( + `Failed to prepare managed checkout for "${input.repoUrl}" at "${cwd}": ${reason}${authNote ? ` ${authNote}` : ""}`, + ); + if (isWorkspaceGitScanError(error)) throw new WorkspaceGitScanError(error.code, message); + const connectionFailure = readGitConnectionFailure(error); + if (connectionFailure) throw new GitConnectionFailureError(message, connectionFailure); + throw new Error(message); + } + + try { + await fs.rename(cloneTmpDir, cwd); + } catch (renameError) { + await fs + .rm(cloneTmpDir, { recursive: true, force: true }) + .catch(() => undefined); + if (await hasAdoptableGitDir()) { + return { cwd, warning: null }; + } + const reason = + renameError instanceof Error ? renameError.message : String(renameError); + throw new Error( + `Failed to move managed checkout into place at "${cwd}": ${reason}`, + ); + } + return { cwd, warning: null }; +} + + +export async function ensureManagedRepositoriesIgnored(cwd: string): Promise { + const gitEntry = await fs.lstat(path.join(cwd, ".git")).catch(() => null); + if (!gitEntry) return; + if (gitEntry.isSymbolicLink()) throw new Error("Workspace Git metadata must not be a symlink"); + const excludePath = await execFile("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"], { timeout: 10_000 }) + .then((result) => path.resolve(cwd, result.stdout.trim())); + const exclude = await fs.readFile(excludePath, "utf8").catch(() => ""); + if (!exclude.split(/\r?\n/).includes(`/${PROJECT_REPOSITORIES_DIR}/`)) { + await fs.mkdir(path.dirname(excludePath), { recursive: true }); + await fs.appendFile(excludePath, `\n/${PROJECT_REPOSITORIES_DIR}/\n`); + } +} diff --git a/server/src/services/native-runtime/native-chat-workspace.test.ts b/server/src/services/native-runtime/native-chat-workspace.test.ts index 238b8651da1..00705ae5573 100644 --- a/server/src/services/native-runtime/native-chat-workspace.test.ts +++ b/server/src/services/native-runtime/native-chat-workspace.test.ts @@ -266,6 +266,8 @@ describe("native external chat workspace boundary", () => { path.join(instanceRoot, "workspaces", agentId), ]) { const oldInput = { ...execution, workspace: { cwd } }; + const admittedScope = { ...scopeA, taskRoot: cwd }; + expect(nativeChatWorkspaceCwd(admittedScope, null, false)).toBeNull(); expect( nativeChatWorkspaceMatches({ scope: scopeA, @@ -275,6 +277,8 @@ describe("native external chat workspace boundary", () => { ).toBe(false); expect(oldInput.workspace.cwd).toBe(cwd); } + const legacyTaskRoot = path.join(instanceRoot, "chat-workspaces", companyId, agentId, issueA); + expect(nativeChatWorkspaceCwd({ ...scopeA, taskRoot: legacyTaskRoot }, null, false)).toBe(legacyTaskRoot); }); it("fails closed if an existing task directory is a symlink to another task", async () => { diff --git a/server/src/services/native-runtime/native-chat-workspace.ts b/server/src/services/native-runtime/native-chat-workspace.ts index 26898069e08..e9432818589 100644 --- a/server/src/services/native-runtime/native-chat-workspace.ts +++ b/server/src/services/native-runtime/native-chat-workspace.ts @@ -1,7 +1,7 @@ import { lstat, mkdir, realpath } from "node:fs/promises"; import path from "node:path"; import { and, eq, sql } from "drizzle-orm"; -import { chatConversations, issues, type Db } from "@paperclipai/db"; +import { chatConversations, issues, executionWorkspaces, projectWorkspaces, type Db } from "@paperclipai/db"; import { resolvePaperclipInstanceRoot } from "../../home-paths.js"; export type NativeChatWorkspaceScope = { @@ -30,6 +30,7 @@ export async function findNativeChatWorkspaceScope( agentId: string; issueId: string | null; instanceRoot?: string; + admittedCwd?: string | null; }, ): Promise { if ( @@ -43,6 +44,19 @@ export async function findNativeChatWorkspaceScope( id: issues.id, projectId: issues.projectId, originKind: issues.originKind, + selection: issues.workspaceSelection, + configuredProjectId: projectWorkspaces.projectId, + hasConfiguredDefault: sql`exists ( + select 1 from ${projectWorkspaces} + where ${projectWorkspaces.projectId} = ${issues.projectId} + and ${projectWorkspaces.companyId} = ${input.companyId} + and (nullif(btrim(${projectWorkspaces.cwd}), '') is not null + or nullif(btrim(${projectWorkspaces.repoUrl}), '') is not null) + )`, + boundWorkspaceId: executionWorkspaces.id, + boundProjectId: executionWorkspaces.projectId, + boundSourceIssueId: executionWorkspaces.sourceIssueId, + boundCwd: executionWorkspaces.cwd, hasConversation: sql`exists ( select 1 from ${chatConversations} where ${chatConversations.issueId} = ${issues.id} @@ -50,6 +64,8 @@ export async function findNativeChatWorkspaceScope( )`, }) .from(issues) + .leftJoin(projectWorkspaces, and(eq(projectWorkspaces.id, issues.projectWorkspaceId), eq(projectWorkspaces.companyId, input.companyId))) + .leftJoin(executionWorkspaces, and(eq(executionWorkspaces.id, issues.executionWorkspaceId), eq(executionWorkspaces.companyId, input.companyId))) .where( and(eq(issues.id, input.issueId), eq(issues.companyId, input.companyId)), ) @@ -64,23 +80,18 @@ export async function findNativeChatWorkspaceScope( companyId: input.companyId, agentId: input.agentId, issueId: issue.id, - projectId: issue.projectId, + projectId: issue.boundWorkspaceId ? issue.boundProjectId : issue.selection?.selection.kind === "task_directory" ? null : issue.configuredProjectId ?? (issue.hasConfiguredDefault ? issue.projectId : null), instanceRoot, // This must be a sibling of agent homes, not a child readable by an old // provider thread whose immutable permission root is the entire agent home. - taskRoot: path.join( - instanceRoot, - "chat-workspaces", - segment(input.companyId), - segment(input.agentId), - segment(issue.id), - ), + taskRoot: issue.boundSourceIssueId === issue.id && issue.boundCwd + ? issue.boundCwd : input.admittedCwd ?? path.join(instanceRoot, "isolated-workspaces", segment(input.companyId), segment(issue.id)), }; } export type NativeChatProjectWorkspace = { companyId: string; - projectId: string; + projectId: string | null; sourceIssueId: string | null; mode: string; strategyType: string; @@ -89,13 +100,26 @@ export type NativeChatProjectWorkspace = { providerRef: string | null; }; +function isNativeChatTaskRoot(scope: NativeChatWorkspaceScope): boolean { + const relative = path.relative(scope.instanceRoot, scope.taskRoot); + const allowedTask = path.join("isolated-workspaces", segment(scope.companyId), segment(scope.issueId)); + const legacyParts = relative.split(path.sep); + return relative === allowedTask || ( + legacyParts.length === 4 && legacyParts[0] === "chat-workspaces" && + legacyParts[1] === scope.companyId && /^[a-zA-Z0-9_-]+$/.test(legacyParts[2]!) && legacyParts[3] === scope.issueId + ); +} + /** No implicit move from an intentionally configured repository to an empty cwd. */ export function nativeChatWorkspaceCwd( scope: NativeChatWorkspaceScope, workspace: NativeChatProjectWorkspace | null, reuseExisting: boolean, ): string | null { - if (!scope.projectId) return workspace === null ? scope.taskRoot : null; + if (!scope.projectId) return isNativeChatTaskRoot(scope) && (workspace === null || ( + workspace.companyId === scope.companyId && workspace.sourceIssueId === scope.issueId && + workspace.projectId === null && workspace.cwd === scope.taskRoot && ["active", "idle"].includes(workspace.status) + )) ? scope.taskRoot : null; if ( !reuseExisting || !workspace || @@ -120,12 +144,9 @@ export async function materializeNativeChatTaskRoot( if (scope.projectId) throw new Error("native_chat_workspace_project_requires_isolation"); let cursor = scope.instanceRoot; - for (const part of [ - "chat-workspaces", - segment(scope.companyId), - segment(scope.agentId), - segment(scope.issueId), - ]) { + const relative = path.relative(scope.instanceRoot, scope.taskRoot); + if (!isNativeChatTaskRoot(scope)) throw new Error("native_chat_workspace_path_not_isolated"); + for (const part of relative.split(path.sep)) { cursor = path.join(cursor, part); await mkdir(cursor, { mode: 0o700 }).catch( (error: NodeJS.ErrnoException) => { diff --git a/server/src/services/native-runtime/native-run-trace.test.ts b/server/src/services/native-runtime/native-run-trace.test.ts index b5c8bd372c0..a5554167c36 100644 --- a/server/src/services/native-runtime/native-run-trace.test.ts +++ b/server/src/services/native-runtime/native-run-trace.test.ts @@ -80,7 +80,7 @@ describe("native runner performance trace", () => { }); expect(ingress).toEqual({ name: "question_response.to_run_created", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: answeredAtMs, endedAtMs: answeredAtMs + 10, }); @@ -120,7 +120,7 @@ describe("native runner performance trace", () => { clock.mockRestore(); } expect( - events.find((event) => event.payload?.span === "task.run.measured") + events.find((event) => event.payload?.span === "task.provider_session.measured") ?.payload, ).toMatchObject({ durationMs: 19_000 }); expect(original.createdAt).toBe(new Date(1_000).toISOString()); @@ -144,7 +144,7 @@ describe("native runner performance trace", () => { }), ).toEqual({ name: "comment.to_run_created", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: 1_000, endedAtMs: 3_000, }); @@ -210,7 +210,7 @@ describe("native runner performance trace", () => { }, }); const prepare = trace.start("task.prepare", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: starts.preparationStartedAtMs, }); await trace.end(prepare, { endedAtMs: attemptStartedAtMs + 50 }); @@ -263,7 +263,7 @@ describe("native runner performance trace", () => { schema: "paperclip.run-performance-span.v1", traceSchemaVersion: NATIVE_RUN_TRACE_SCHEMA_VERSION, span: "runner.transport.selected", - parentSpan: "task.run", + parentSpan: "task.provider_session", startOffsetMs: 125, durationMs: 0, outcome: "ok", @@ -304,7 +304,7 @@ describe("native runner performance trace", () => { const { traceContext, spans } = createRecordingTraceContext(); const trace = createNativeRunTrace({ runId: "skills-run", startedAtMs: 100, traceContext, onEvent: async () => { throw new Error("run log unavailable"); } }); - const preparation = trace.start("task.prepare", { parentName: "task.run", startedAtMs: 100 }); + const preparation = trace.start("task.prepare", { parentName: "task.provider_session", startedAtMs: 100 }); await expect(trace.record({ name: "skills.prepare", parentName: "task.prepare", startedAtMs: 120, endedAtMs: 170 })).resolves.toBeUndefined(); await trace.end(preparation, { endedAtMs: 200 }); expect(spans.find((span) => span.name === "skills.prepare")).toMatchObject({ name: "skills.prepare", parentName: "task.prepare", endedAtMs: 170 }); @@ -350,7 +350,7 @@ describe("native runner performance trace", () => { }); const prepare = trace.start("task.prepare", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: 1_010, }); const environment = trace.start("environment.startup", { @@ -367,7 +367,7 @@ describe("native runner performance trace", () => { await trace.end(prepare, { endedAtMs: 1_040 }); const execute = trace.start("native.session.execute", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: 1_050, }); const startup = trace.start("runner.session.startup", { @@ -417,11 +417,11 @@ describe("native runner performance trace", () => { const parentOf = (name: string) => spans.find((span) => span.name === name)?.parentName; - expect(parentOf("task.run")).toBeNull(); - expect(parentOf("task.prepare")).toBe("task.run"); + expect(parentOf("task.provider_session")).toBeNull(); + expect(parentOf("task.prepare")).toBe("task.provider_session"); expect(parentOf("environment.startup")).toBe("task.prepare"); expect(parentOf("environment.acquire")).toBe("environment.startup"); - expect(parentOf("native.session.execute")).toBe("task.run"); + expect(parentOf("native.session.execute")).toBe("task.provider_session"); expect(parentOf("runner.session.startup")).toBe("native.session.execute"); expect(parentOf("runner.artifact.prepare")).toBe("runner.session.startup"); expect(parentOf("runner.artifact.discover")).toBe( @@ -437,10 +437,10 @@ describe("native runner performance trace", () => { expect(parentOf("provider.dynamic")).toBe("agent.turn"); expect(parentOf("provider.turn.queue")).toBe("agent.turn"); - expect(spans.filter((span) => span.name === "task.run")).toHaveLength(1); - expect(spans.some((span) => span.name === "task.run.measured")).toBe(false); + expect(spans.filter((span) => span.name === "task.provider_session")).toHaveLength(1); + expect(spans.some((span) => span.name === "task.provider_session.measured")).toBe(false); expect( - events.some((event) => event.payload?.span === "task.run.measured"), + events.some((event) => event.payload?.span === "task.provider_session.measured"), ).toBe(true); }); }); diff --git a/server/src/services/native-runtime/native-run-trace.ts b/server/src/services/native-runtime/native-run-trace.ts index b130f26cdaa..e3f4f8291b0 100644 --- a/server/src/services/native-runtime/native-run-trace.ts +++ b/server/src/services/native-runtime/native-run-trace.ts @@ -9,7 +9,7 @@ import { } from "../../instrumentation.js"; export const NATIVE_RUN_SPAN_EVENT_TYPE = "run.performance.span"; -export const NATIVE_RUN_TRACE_SCHEMA_VERSION = 2; +export const NATIVE_RUN_TRACE_SCHEMA_VERSION = 3; export type NativeRunSpanOutcome = "ok" | "failed"; @@ -41,7 +41,7 @@ export function buildNativeWakeIngressSpan(input: { if (answeredAt !== null && Number.isFinite(answeredAt) && answeredAt >= 0) { return { name: "question_response.to_run_created", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: answeredAt, endedAtMs: Math.max(answeredAt, input.runCreatedAtMs), }; @@ -60,7 +60,7 @@ export function buildNativeWakeIngressSpan(input: { ? null : { name: "comment.to_run_created", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: createdAt, endedAtMs: Math.max(createdAt, input.runCreatedAtMs), }; @@ -77,13 +77,13 @@ export function buildNativeHeartbeatPreparationSpans(input: { return [ { name: "heartbeat.queue", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: input.runCreatedAtMs, endedAtMs: Math.max(input.runCreatedAtMs, input.runStartedAtMs), }, { name: "heartbeat.prepare_before_environment", - parentName: "task.run", + parentName: "task.provider_session", // A same-run resume retains startedAt for wall-time accounting; it is // not the beginning of this dispatch attempt's preparation. startedAtMs: input.attemptStartedAtMs, @@ -94,7 +94,7 @@ export function buildNativeHeartbeatPreparationSpans(input: { }, { name: "heartbeat.prepare_after_environment", - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: Math.min( input.nativeDispatchAtMs, input.environmentRealizeEndedAtMs, @@ -210,7 +210,7 @@ export function createNativeRunTrace(input: { const runIdHash = hashedId(input.runId); let rootSpan: SpanHandle; try { - rootSpan = tracing.tracer.startSpan("task.run", { + rootSpan = tracing.tracer.startSpan("task.provider_session", { startTime: traceStartedAtMs, attributes: { "paperclip.task.run.run_id": runIdHash, @@ -249,19 +249,19 @@ export function createNativeRunTrace(input: { ? { name: active.name, context: active.context } : runtimeParentScope && !runtimeParentScope.ended ? { name: runtimeParentScope.name, context: runtimeParentScope.context } - : { name: "task.run", context: rootContext }; + : { name: "task.provider_session", context: rootContext }; }; const parentFor = ( parentName?: string, ): { name: string; context: unknown } => { - if (parentName && parentName !== "task.run") { + if (parentName && parentName !== "task.provider_session") { const named = namedScopes.get(parentName); if (named) return { name: named.name, context: named.context }; return currentParent(); } - if (parentName === "task.run") - return { name: "task.run", context: rootContext }; + if (parentName === "task.provider_session") + return { name: "task.provider_session", context: rootContext }; return currentParent(); }; @@ -298,7 +298,7 @@ export function createNativeRunTrace(input: { schema: "paperclip.run-performance-span.v1", traceSchemaVersion: NATIVE_RUN_TRACE_SCHEMA_VERSION, span: span.name, - parentSpan: span.parentName ?? "task.run", + parentSpan: span.parentName ?? "task.provider_session", startOffsetMs: startedAtMs - traceStartedAtMs, durationMs, outcome, @@ -508,8 +508,8 @@ export function createNativeRunTrace(input: { // Preserve the historical run-log record without emitting a duplicate // full-width child span into the OpenTelemetry waterfall. await emitCompletedSpanEvent({ - name: "task.run.measured", - parentName: "task.run", + name: "task.provider_session.measured", + parentName: "task.provider_session", startedAtMs: traceStartedAtMs, endedAtMs, outcome, @@ -541,7 +541,7 @@ export async function recordFailedSkillPreparation(input: { }): Promise { try { const trace = createNativeRunTrace(input); - const preparation = trace.start("task.prepare", { parentName: "task.run", startedAtMs: input.startedAtMs }); + const preparation = trace.start("task.prepare", { parentName: "task.provider_session", startedAtMs: input.startedAtMs }); const endedAtMs = Date.now(); await trace.record({ name: "skills.prepare", parentName: "task.prepare", startedAtMs: input.startedAtMs, endedAtMs, outcome: "failed" }); await trace.end(preparation, { endedAtMs, outcome: "failed" }); diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 52844fcdd63..52b40ff1c77 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -5848,7 +5848,7 @@ describe("native resumed preparation timing", () => { const payloadFor = (span: string) => events.find((event) => event.payload?.span === span)?.payload; expect(payloadFor("question_response.to_run_created")).toMatchObject({ - parentSpan: "task.run", + parentSpan: "task.provider_session", durationMs: 50, startOffsetMs: 0, }); @@ -5856,7 +5856,7 @@ describe("native resumed preparation timing", () => { durationMs: 30, startOffsetMs: 70, }); - expect(payloadFor("task.run.measured")).toMatchObject({ durationMs: 100 }); + expect(payloadFor("task.provider_session.measured")).toMatchObject({ durationMs: 100 }); }); it("uses attempt-local preparation in the executor without truncating run elapsed time", async () => { @@ -5910,7 +5910,7 @@ describe("native resumed preparation timing", () => { durationMs: 1_000, startOffsetMs: 0, }); - expect(payloadFor("task.run.measured")).toMatchObject({ + expect(payloadFor("task.provider_session.measured")).toMatchObject({ durationMs: 984_050, }); }); diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 92501cfa942..0a31738bd6c 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -7696,7 +7696,7 @@ async function executePaperclipNativeSessionWithinScope( }); const toolTrace = createNativeToolTrace(trace); const taskPrepareScope = trace.start("task.prepare", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: preparationStarts.preparationStartedAtMs, }); const environmentSpans = preparationSpans.filter( @@ -7724,7 +7724,7 @@ async function executePaperclipNativeSessionWithinScope( await trace.record({ ...span, parentName: rootMilestone - ? "task.run" + ? "task.provider_session" : environmentSpans.includes(span) ? "environment.startup" : "task.prepare", @@ -8285,7 +8285,7 @@ async function executePaperclipNativeSessionWithinScope( outcome, }); taskSettleScope = trace.start("task.settle", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: milestoneAtMs, }); trace.activate(taskSettleScope); @@ -8955,7 +8955,7 @@ async function executePaperclipNativeSessionWithinScope( }); return result; }, - { parentName: "task.run" }, + { parentName: "task.provider_session" }, ); // A detached consumer can resolve successfully after its stream closes. // Only the replacement controller may settle the run or certify accounting. @@ -9059,7 +9059,7 @@ async function executePaperclipNativeSessionWithinScope( ).slice(-4_096); if (!taskSettleScope) { taskSettleScope = trace.start("task.settle", { - parentName: "task.run", + parentName: "task.provider_session", startedAtMs: failedAtMs, }); } diff --git a/server/src/services/native-runtime/native-workspace-sync.ts b/server/src/services/native-runtime/native-workspace-sync.ts index b6e1aeaf9ca..6841f9f132f 100644 --- a/server/src/services/native-runtime/native-workspace-sync.ts +++ b/server/src/services/native-runtime/native-workspace-sync.ts @@ -1,3 +1,5 @@ +import type { WorkspaceCheckpointMetrics } from "@paperclipai/adapter-utils/workspace-checkpoint"; +import type { RuntimeSpanRunner } from "@paperclipai/adapter-utils/acpx-engine/startup-timing"; import fs from "node:fs/promises"; import { createReadStream } from "node:fs"; import path from "node:path"; @@ -31,8 +33,9 @@ import { parseObject } from "../../adapters/utils.js"; import type { NativeRestartRecoveryClaim } from "./native-restart-recovery.js"; const LEGACY_DESCRIPTOR_SCHEMA = "paperclip.native-workspace-sync/v1"; -const DESCRIPTOR_SCHEMA = "paperclip.native-workspace-sync/v2"; -type DescriptorSchema = typeof DESCRIPTOR_SCHEMA | typeof LEGACY_DESCRIPTOR_SCHEMA; +const PREVIOUS_DESCRIPTOR_SCHEMA = "paperclip.native-workspace-sync/v2"; +const DESCRIPTOR_SCHEMA = "paperclip.native-workspace-sync/v3"; +type DescriptorSchema = typeof DESCRIPTOR_SCHEMA | typeof PREVIOUS_DESCRIPTOR_SCHEMA | typeof LEGACY_DESCRIPTOR_SCHEMA; const STAMP_SCHEMA = "paperclip.native-workspace-stamp/v1"; const STATE_ROOT_NAME = "native-workspace-sync"; const DESCRIPTOR_NAME = "descriptor"; @@ -61,6 +64,7 @@ interface NativeWorkspaceSyncDescriptor { baselineSha256: string; baseline: SerializedDirectorySnapshot; gitSnapshot: GitWorkspaceSnapshot | null; + repositories?: NonNullable; seed: { workspaceArchiveSha256: string; gitArchiveSha256: string | null; @@ -190,6 +194,7 @@ async function verifiedDurableSeed(input: { runId: string; seed: NonNullable; gitSnapshot: GitWorkspaceSnapshot | null; + repositories?: NonNullable; }): Promise { try { const paths = durableSeedPaths(input.runId); @@ -203,7 +208,7 @@ async function verifiedDurableSeed(input: { ) { throw new Error("workspace_sync_out_unrecoverable"); } - if (input.gitSnapshot) { + if (input.gitSnapshot || input.repositories?.length) { const gitStat = await fs.lstat(paths.gitArchivePath); if ( gitStat.isSymbolicLink() || @@ -219,7 +224,7 @@ async function verifiedDurableSeed(input: { return { workspaceArchivePath: paths.workspaceArchivePath, workspaceArchiveSha256: input.seed.workspaceArchiveSha256, - gitArchivePath: input.gitSnapshot ? paths.gitArchivePath : null, + gitArchivePath: input.gitSnapshot || input.repositories?.length ? paths.gitArchivePath : null, gitArchiveSha256: input.seed.gitArchiveSha256, }; } catch (error) { @@ -281,7 +286,7 @@ export function readNativeWorkspaceSyncReference( ): NativeWorkspaceSyncReference | null { const candidate = parseObject(value); if ( - (candidate.schema !== DESCRIPTOR_SCHEMA && candidate.schema !== LEGACY_DESCRIPTOR_SCHEMA) || + (candidate.schema !== DESCRIPTOR_SCHEMA && candidate.schema !== PREVIOUS_DESCRIPTOR_SCHEMA && candidate.schema !== LEGACY_DESCRIPTOR_SCHEMA) || (candidate.state !== "prepared" && candidate.state !== "finalized") || typeof candidate.descriptorSha256 !== "string" || !/^[0-9a-f]{64}$/.test(candidate.descriptorSha256) || @@ -360,6 +365,7 @@ async function persistSnapshotManifests(runId: string, snapshot: NonNullable
();
   for (const manifest of manifests()) {
@@ -385,6 +391,7 @@ async function persistSnapshotManifests(runId: string, snapshot: NonNullable
 validManifestShape(entries) || (Array.isArray(entries) && entries.every((entry) =>
       typeof entry === "string" && entry.length > 0 && !entry.includes("\0") && !path.posix.isAbsolute(entry) && !path.win32.isAbsolute(entry)
       && !entry.split(/[\\/]/).some((segment) => segment === ".." || segment === "."))))) return undefined;
@@ -414,6 +421,7 @@ function parseGitSnapshot(
   return {
     headCommit: candidate.headCommit,
     branchName: candidate.branchName as string | null,
+    ...(candidate.originUrl !== undefined ? { originUrl: candidate.originUrl as string | null } : {}),
     overlayPaths: candidate.overlayPaths as WorkspacePaths,
     deletedPaths: candidate.deletedPaths as WorkspacePaths,
     ignoredPaths: candidate.ignoredPaths as WorkspacePaths,
@@ -464,7 +472,7 @@ async function readDescriptor(input: {
   const binding = parseObject(candidate.binding);
   const rawBaseline = parseObject(candidate.baseline);
   let baseline: DirectorySnapshot | null;
-  if (candidate.schema === DESCRIPTOR_SCHEMA && rawBaseline.version === 2
+  if ((candidate.schema === DESCRIPTOR_SCHEMA || candidate.schema === PREVIOUS_DESCRIPTOR_SCHEMA) && rawBaseline.version === 2
     && Array.isArray(rawBaseline.exclude) && rawBaseline.exclude.every((entry) => typeof entry === "string")
     && validManifestShape(rawBaseline.entries) && rawBaseline.entries.category === "baseline"
     && (rawBaseline.ignoredPaths === undefined || validManifestShape(rawBaseline.ignoredPaths))) {
@@ -475,6 +483,12 @@ async function readDescriptor(input: {
     baseline = parseDirectorySnapshot(candidate.baseline);
   }
   const gitSnapshot = parseGitSnapshot(candidate.gitSnapshot);
+  const repositoryContainer = candidate.repositories === undefined ? null : parseGitSnapshot({
+    headCommit: "", branchName: null, overlayPaths: [], deletedPaths: [], ignoredPaths: [], repositories: candidate.repositories,
+  });
+  if (candidate.repositories !== undefined && (!repositoryContainer || candidate.schema !== DESCRIPTOR_SCHEMA || gitSnapshot)) throw new Error("native_workspace_sync_repositories_invalid");
+  const repositories = repositoryContainer?.repositories;
+  for (const repo of repositories ?? []) for (const manifest of gitManifests(repo.snapshot)) await verifyManifest(input.runId, manifest);
   for (const manifest of gitManifests(gitSnapshot ?? null)) await verifyManifest(input.runId, manifest);
   const rawSeed =
     candidate.seed === null || candidate.seed === undefined
@@ -509,7 +523,7 @@ async function readDescriptor(input: {
         ? null
         : undefined;
   if (
-    (candidate.schema !== DESCRIPTOR_SCHEMA && candidate.schema !== LEGACY_DESCRIPTOR_SCHEMA) ||
+    (candidate.schema !== DESCRIPTOR_SCHEMA && candidate.schema !== PREVIOUS_DESCRIPTOR_SCHEMA && candidate.schema !== LEGACY_DESCRIPTOR_SCHEMA) ||
     (candidate.state !== "prepared" && candidate.state !== "finalized") ||
     !baseline ||
     gitSnapshot === undefined ||
@@ -560,6 +574,7 @@ async function readDescriptor(input: {
       baselineSha256: candidate.baselineSha256,
       baseline: candidate.baseline as SerializedDirectorySnapshot,
       gitSnapshot,
+      ...(repositories?.length ? { repositories } : {}),
       seed,
       createdAt: candidate.createdAt,
       finalizedAt:
@@ -681,6 +696,7 @@ async function remoteStampMatches(input: {
   target: Extract;
   expected: Record;
   gitSnapshot?: GitWorkspaceSnapshot | null;
+  repositories?: NonNullable;
 }): Promise {
   if (!input.target.runner) return false;
   const stampPath = path.posix.join(
@@ -699,11 +715,15 @@ async function remoteStampMatches(input: {
     gitChecks.push(snapshot.branchName === null
       ? `(${git} symbolic-ref --quiet HEAD >/dev/null 2>&1; test $? -eq 1)`
       : `test "$(${git} symbolic-ref --quiet --short HEAD)" = ${shellQuote(snapshot.branchName)}`);
+    if (snapshot.originUrl !== undefined) gitChecks.push(snapshot.originUrl === null
+      ? `! ${git} config --get remote.origin.url >/dev/null 2>&1`
+      : `test "$(${git} config --get remote.origin.url)" = ${shellQuote(snapshot.originUrl)}`);
     for (const repository of snapshot.repositories ?? []) {
       checkGit(path.posix.join(remoteDir, repository.path), repository.snapshot);
     }
   };
   if (input.gitSnapshot) checkGit(input.target.remoteCwd, input.gitSnapshot);
+  for (const repo of input.repositories ?? []) checkGit(path.posix.join(input.target.remoteCwd, repo.path), repo.snapshot);
   const result = await input.target.runner.execute({
     command: input.target.shellCommand ?? "sh",
     args: [
@@ -767,7 +787,12 @@ async function prepareRuntime(input: {
   mode: WorkspaceInboundMode;
   baseline?: DirectorySnapshot;
   gitSnapshot?: GitWorkspaceSnapshot | null;
+  repositories?: NonNullable;
   durableSeed?: WorkspaceDurableSeedPaths;
+  seedCacheDirectory?: string;
+  seedCacheCompanyDirectory?: string;
+  onPhase?: RuntimeSpanRunner;
+  onCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise;
 }): Promise {
   return prepareAdapterExecutionTargetRuntime({
     runId: input.runId,
@@ -779,6 +804,12 @@ async function prepareRuntime(input: {
     workspaceDurableSeed: input.durableSeed,
     workspaceBaseline: input.baseline,
     workspaceGitSnapshot: input.gitSnapshot,
+    workspaceRepositories: input.repositories ?? (input.baseline ? [] : undefined),
+    workspaceCheckpoint: true,
+    onWorkspaceCheckpoint: input.onCheckpoint,
+    workspaceSeedCacheDirectory: input.seedCacheDirectory,
+    workspaceSeedCacheCompanyDirectory: input.seedCacheCompanyDirectory,
+    runtimeSpan: input.onPhase,
   });
 }
 
@@ -789,8 +820,11 @@ async function finalizePreparedRuntime(input: {
   runtime: PreparedAdapterExecutionTargetRuntime;
   descriptor: NativeWorkspaceSyncDescriptor;
   assertOwnership?: () => Promise;
+  onPhase?: RuntimeSpanRunner;
+  onCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise;
 }): Promise {
-  await input.runtime.restoreWorkspace();
+  if (input.onPhase) await input.onPhase("workspace.restore", () => input.runtime.restoreWorkspace());
+  else await input.runtime.restoreWorkspace();
   await input.assertOwnership?.();
   const finalSnapshot =
     await import("@paperclipai/adapter-utils/workspace-restore-merge").then(
@@ -836,6 +870,8 @@ export async function prepareNativeWorkspaceSync(input: {
   restartRecovery?: NativeRestartRecoveryClaim;
   sameRunRecovery?: boolean;
   resourceDisposition?: NativeWorkspaceResourceDisposition;
+  onPhase?: RuntimeSpanRunner;
+  onCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise;
 }): Promise {
   if (input.target?.kind !== "remote" || input.target.transport !== "sandbox") {
     return null;
@@ -896,6 +932,7 @@ export async function prepareNativeWorkspaceSync(input: {
               runId: input.runId,
               seed: existing.descriptor.seed,
               gitSnapshot: existing.descriptor.gitSnapshot,
+              repositories: existing.descriptor.repositories,
             })
           : (() => {
               throw new Error("workspace_sync_out_unrecoverable");
@@ -904,10 +941,13 @@ export async function prepareNativeWorkspaceSync(input: {
     runtime = await prepareRuntime({
       runId: input.runId,
       target,
+      onPhase: input.onPhase,
+      onCheckpoint: input.onCheckpoint,
       workspaceLocalDir: input.workspaceLocalDir,
       mode,
       baseline: existing.baseline,
       gitSnapshot: existing.descriptor.gitSnapshot,
+      repositories: existing.descriptor.repositories,
       durableSeed,
     });
     descriptor = {
@@ -934,8 +974,12 @@ export async function prepareNativeWorkspaceSync(input: {
     runtime = await prepareRuntime({
       runId: input.runId,
       target,
+      onPhase: input.onPhase,
+      onCheckpoint: input.onCheckpoint,
       workspaceLocalDir: input.workspaceLocalDir,
       mode,
+      seedCacheCompanyDirectory: path.join(resolvePaperclipInstanceRoot(), STATE_ROOT_NAME, "generations", requireSafeSegment(input.companyId, "company_id")),
+      seedCacheDirectory: path.join(resolvePaperclipInstanceRoot(), STATE_ROOT_NAME, "generations", requireSafeSegment(input.companyId, "company_id"), requireSafeSegment(input.workspaceId, "workspace_id")),
       durableSeed: {
         workspaceArchivePath: seedPaths.workspaceArchivePath,
         gitArchivePath: seedPaths.gitArchivePath,
@@ -951,7 +995,7 @@ export async function prepareNativeWorkspaceSync(input: {
       );
       const verifiedWarmAdoption =
         priorStamp.hostSha256 === currentHostSha256 &&
-        (await remoteStampMatches({ target, expected: priorStamp, gitSnapshot: currentSnapshot.gitSnapshot }));
+        (await remoteStampMatches({ target, expected: priorStamp, gitSnapshot: currentSnapshot.gitSnapshot, repositories: currentSnapshot.repositories }));
       if (verifiedWarmAdoption) {
         mode = "adopt_remote";
       } else {
@@ -963,6 +1007,9 @@ export async function prepareNativeWorkspaceSync(input: {
           mode,
           baseline: currentSnapshot.baseline,
           gitSnapshot: currentSnapshot.gitSnapshot,
+          repositories: currentSnapshot.repositories,
+          onPhase: input.onPhase,
+          onCheckpoint: input.onCheckpoint,
           durableSeed: {
             workspaceArchivePath: seedPaths.workspaceArchivePath,
             gitArchivePath: seedPaths.gitArchivePath,
@@ -977,7 +1024,7 @@ export async function prepareNativeWorkspaceSync(input: {
     const workspaceArchiveSha256 = await sha256File(
       seedPaths.workspaceArchivePath,
     );
-    const gitArchiveSha256 = snapshot.gitSnapshot
+    const gitArchiveSha256 = snapshot.gitSnapshot || snapshot.repositories?.length
       ? await sha256File(seedPaths.gitArchivePath)
       : null;
     descriptor = {
@@ -995,6 +1042,7 @@ export async function prepareNativeWorkspaceSync(input: {
       baselineSha256: directorySnapshotSha256(snapshot.baseline),
       baseline: serializeDirectorySnapshot(snapshot.baseline),
       gitSnapshot: snapshot.gitSnapshot,
+      ...(snapshot.repositories?.length ? { repositories: snapshot.repositories } : {}),
       seed: { workspaceArchiveSha256, gitArchiveSha256 },
       createdAt: now,
       finalizedAt: null,
@@ -1021,6 +1069,8 @@ export async function prepareNativeWorkspaceSync(input: {
           runtime: preparedRuntime,
           descriptor,
           assertOwnership,
+          onPhase: input.onPhase,
+          onCheckpoint: input.onCheckpoint,
         })
           .then((finalizedReference) => {
             reference = finalizedReference;
@@ -1041,11 +1091,35 @@ export async function prepareNativeWorkspaceSync(input: {
   }
 }
 
+export async function readNativeWorkspaceSyncLocalCwd(input: {
+  companyId: string;
+  runId: string;
+  workspaceId: string;
+  reference: NativeWorkspaceSyncReference;
+}): Promise {
+  const existing = await readDescriptor(input);
+  try {
+    if (existing.descriptor.binding.companyId !== input.companyId ||
+        existing.descriptor.binding.workspaceId !== input.workspaceId) {
+      throw new Error("native_workspace_sync_descriptor_binding_mismatch");
+    }
+    return existing.descriptor.binding.localCwd;
+  } finally {
+    await disposeDirectorySnapshot(existing.baseline);
+    await disposeGitWorkspaceSnapshot(existing.descriptor.gitSnapshot);
+    for (const repository of existing.descriptor.repositories ?? []) {
+      await disposeGitWorkspaceSnapshot(repository.snapshot);
+    }
+  }
+}
+
 export async function resumeNativeWorkspaceSync(input: {
   db: Db;
   runId: string;
   target: AdapterExecutionTarget;
   assertOwnership?: () => Promise;
+  onPhase?: RuntimeSpanRunner;
+  onCheckpoint?: (metrics: WorkspaceCheckpointMetrics) => Promise;
 }): Promise {
   if (input.target.kind !== "remote" || input.target.transport !== "sandbox") {
     throw new Error("workspace_sync_out_unrecoverable");
@@ -1094,10 +1168,13 @@ export async function resumeNativeWorkspaceSync(input: {
   const runtime = await prepareRuntime({
     runId: input.runId,
     target: input.target,
+    onPhase: input.onPhase,
+    onCheckpoint: input.onCheckpoint,
     workspaceLocalDir: existing.descriptor.binding.localCwd,
     mode: "adopt_remote",
     baseline: existing.baseline,
     gitSnapshot: existing.descriptor.gitSnapshot,
+    repositories: existing.descriptor.repositories,
   });
   await finalizePreparedRuntime({
     db: input.db,
@@ -1106,6 +1183,8 @@ export async function resumeNativeWorkspaceSync(input: {
     runtime,
     descriptor: existing.descriptor,
     assertOwnership: input.assertOwnership,
+    onPhase: input.onPhase,
+    onCheckpoint: input.onCheckpoint,
   });
   return true;
 }
diff --git a/server/src/services/native-runtime/paperclip-runner-tool-authority.test.ts b/server/src/services/native-runtime/paperclip-runner-tool-authority.test.ts
index 018c93dafaa..39471e2da24 100644
--- a/server/src/services/native-runtime/paperclip-runner-tool-authority.test.ts
+++ b/server/src/services/native-runtime/paperclip-runner-tool-authority.test.ts
@@ -103,7 +103,10 @@ describe("PaperclipRunnerToolAuthority", () => {
       issueId,
       runId,
     });
-    expect(authority.definitions()).toHaveLength(44);
+    expect(authority.definitions()).toHaveLength(48);
+    expect(authority.definitions().map(tool => tool.name)).toEqual(expect.arrayContaining([
+      "get_workspace", "list_workspaces", "select_workspace", "prepare_repository",
+    ]));
     expect(authority.definitions().map(tool => tool.name)).not.toContain("read_chat_attachment");
     expect(authority.definitions().map(tool => tool.name)).not.toContain("read_current_wake_comments");
     const questions = authority.definitions().find(tool => tool.name === "request_human_input")!;
diff --git a/server/src/services/native-runtime/paperclip-runner-tool-authority.ts b/server/src/services/native-runtime/paperclip-runner-tool-authority.ts
index de950a6b7ab..e8f2e574f42 100644
--- a/server/src/services/native-runtime/paperclip-runner-tool-authority.ts
+++ b/server/src/services/native-runtime/paperclip-runner-tool-authority.ts
@@ -1,3 +1,4 @@
+import { callTaskWorkspaceTool } from "../task-workspace-tools.js";
 import { TaskAttachmentReadCache, TASK_ATTACHMENT_DEFINITIONS, TASK_ATTACHMENT_SCHEMAS, listTaskAttachments, readTaskAttachment } from "./runner-task-attachments.js";
 import { RUNNER_BRIDGE_SCHEMAS, runnerBridgeDefinitions, readAssignedSkill, executeWorkspaceTool, readWorkspaceUploadFile, settleRunnerBridgeRead, assertRunnerWorkspacePathAllowed } from "./runner-bridge-tools.js";
 import type { NativeRuntimeContextSnapshot } from "../../vendor/paperclip-runner/index.js";
@@ -110,7 +111,7 @@ const IMPLEMENTED_OPERATIONS = new Set([
   "search_api", "call_api", "hire_agent",
   "get_task_context", "get_task_history", "search_tasks", "report_progress", "set_task_title", "set_task_monitor",
   "request_human_input",
-  "create_skill", "update_skill", "create_task", "reassign_task", "set_dependencies", "create_project", "list_project_repositories", "list_projects", "register_deliverable",
+  "get_workspace", "list_workspaces", "select_workspace", "prepare_repository", "create_skill", "update_skill", "create_task", "reassign_task", "set_dependencies", "create_project", "list_project_repositories", "list_projects", "register_deliverable",
   "list_documents", "read_document", "list_document_revisions", "write_document",
   "list_agents", "get_agent", "list_approvals", "get_approval", "get_approval_context",
 ]);
@@ -507,6 +508,15 @@ export class PaperclipRunnerToolAuthority {
         if (!apiUrl || !token) throw new Error("Skill tool authentication is unavailable");
         return callUpdateSkillTool({ arguments: input, apiUrl, token, companyId: this.binding.companyId });
       }
+      case "get_workspace":
+      case "list_workspaces":
+      case "select_workspace":
+      case "prepare_repository": {
+        const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
+        const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);
+        if (!apiUrl || !token) throw new Error("Workspace tool authentication is unavailable");
+        return callTaskWorkspaceTool({ name: call.tool, arguments: input, apiUrl, token, companyId: this.binding.companyId, issueId: this.binding.issueId, workspaceRoot: this.binding.workspaceRoot });
+      }
       case "create_project":
       case "list_project_repositories":
       case "list_projects": {
@@ -1219,6 +1229,7 @@ export class PaperclipRunnerToolAuthority {
       const createInput = {
         id: issueId,
         projectId,
+        workspaceSelectionActor: this.#privacyActor(context.run),
         ...(trust.kind === "low_trust_review" ? {
           executionPolicy: { ...normalizeIssueExecutionPolicy({ authorizationPolicy: { trustPreset: trust.preset, trustBoundary: trust.boundary } }) },
           sourceTrust: buildLowTrustSourceTrust({ issueId, runId: this.binding.runId, agentId: this.binding.agentId }),
diff --git a/server/src/services/project-default-workspace.ts b/server/src/services/project-default-workspace.ts
new file mode 100644
index 00000000000..44a3d59987d
--- /dev/null
+++ b/server/src/services/project-default-workspace.ts
@@ -0,0 +1,23 @@
+import { projects, projectWorkspaces, type Db } from "@paperclipai/db";
+import { and, asc, desc, eq } from "drizzle-orm";
+import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js";
+
+type Reader = Db | Parameters[0]>[0];
+
+export async function resolveProjectDefaultWorkspaceId(db: Reader, input: {
+  companyId: string;
+  projectId: string;
+  policy?: ReturnType;
+}): Promise {
+  let policy = input.policy;
+  if (policy === undefined) {
+    const [project] = await db.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy }).from(projects)
+      .where(and(eq(projects.companyId, input.companyId), eq(projects.id, input.projectId)));
+    policy = parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy);
+  }
+  if (policy?.defaultProjectWorkspaceId) return policy.defaultProjectWorkspaceId;
+  const [workspace] = await db.select({ id: projectWorkspaces.id }).from(projectWorkspaces)
+    .where(and(eq(projectWorkspaces.companyId, input.companyId), eq(projectWorkspaces.projectId, input.projectId)))
+    .orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id)).limit(1);
+  return workspace?.id ?? null;
+}
diff --git a/server/src/services/projects.ts b/server/src/services/projects.ts
index b5cd4d2445a..cdd88c948c1 100644
--- a/server/src/services/projects.ts
+++ b/server/src/services/projects.ts
@@ -2,6 +2,7 @@ import { and, asc, desc, eq, gt, inArray, isNull, or, sql } from "drizzle-orm";
 import type { Db } from "@paperclipai/db";
 import {
   projects,
+  executionWorkspaces,
   projectAccessMembers,
   projectGoals,
   goals,
@@ -30,12 +31,22 @@ import {
   type PluginManagedProjectDeclaration,
   type PluginManagedProjectResolution,
 } from "@paperclipai/shared";
-import { unprocessable } from "../errors.js";
+import { conflict, unprocessable } from "../errors.js";
 import { listCurrentRuntimeServicesForProjectWorkspaces } from "./workspace-runtime-read-model.js";
 import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js";
 import { mergeProjectWorkspaceRuntimeConfig, readProjectWorkspaceRuntimeConfig } from "./project-workspace-runtime-config.js";
 import { resolveManagedProjectWorkspaceDir } from "../home-paths.js";
 import { recordProjectLifecycleEvent, recordResourceCreationEvent } from "./resource-lifecycle-events.js";
+import { lockExecutionWorkspaceRootProvenance, preserveConfiguredProjectWorkspaceRootProvenance } from "./execution-workspace-repositories.js";
+
+type ProjectTransaction = Parameters[0]>[0];
+
+async function lockProjectRootMutation(tx: ProjectTransaction, projectId: string) {
+  const [identity] = await tx.select({ companyId: projects.companyId }).from(projects).where(eq(projects.id, projectId));
+  if (!identity) return null;
+  await lockExecutionWorkspaceRootProvenance(tx, identity.companyId);
+  return tx.select().from(projects).where(eq(projects.id, projectId)).for("update").then(rows => rows[0] ?? null);
+}
 
 type ProjectRow = typeof projects.$inferSelect;
 type ProjectWorkspaceRow = typeof projectWorkspaces.$inferSelect;
@@ -913,7 +924,7 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
 
     replaceRepositories: async (projectId: string, repositories: import("@paperclipai/shared").ProjectRepository[]): Promise => {
       return db.transaction(async (tx) => {
-        const [project] = await tx.select().from(projects).where(eq(projects.id, projectId)).for("update");
+        const project = await lockProjectRootMutation(tx, projectId);
         if (!project) return null;
         const service = projectService(tx as unknown as Db, { captureWorkspaceUpdates: false });
         const existing = await service.listWorkspaces(projectId);
@@ -1054,16 +1065,17 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
       return cleared;
     },
 
-    remove: (id: string) =>
-      db
-        .delete(projects)
-        .where(eq(projects.id, id))
-        .returning()
-        .then((rows) => {
-          const row = rows[0] ?? null;
-          if (!row) return null;
-          return { ...row, urlKey: deriveProjectUrlKey(row.name, row.id) };
-        }),
+    remove: async (id: string) => db.transaction(async tx => {
+      const project = await lockProjectRootMutation(tx, id);
+      if (!project) return null;
+      const [retained] = await tx.select({ id: executionWorkspaces.id }).from(executionWorkspaces)
+        .where(eq(executionWorkspaces.projectId, id)).limit(1);
+      if (retained) throw conflict("This project still supplies access policy for retained task workspaces. Archive the project or explicitly clean up its workspaces before deleting it.");
+      const workspaces = await tx.select().from(projectWorkspaces).where(eq(projectWorkspaces.projectId, id)).for("update");
+      for (const workspace of workspaces) await preserveConfiguredProjectWorkspaceRootProvenance(tx, workspace);
+      const [row] = await tx.delete(projects).where(eq(projects.id, id)).returning();
+      return row ? { ...row, urlKey: deriveProjectUrlKey(row.name, row.id) } : null;
+    }),
 
     listWorkspaces: async (projectId: string): Promise => {
       const rows = await db
@@ -1176,7 +1188,7 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
       data: UpdateWorkspaceInput,
     ): Promise => db.transaction(async tx => {
       const db = tx as unknown as Db;
-      await db.select({ id: projects.id }).from(projects).where(eq(projects.id, projectId)).for("update");
+      if (!await lockProjectRootMutation(tx, projectId)) return null;
       const existing = await db
         .select()
         .from(projectWorkspaces)
@@ -1186,6 +1198,7 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
             eq(projectWorkspaces.projectId, projectId),
           ),
         )
+        .for("update")
         .then((rows) => rows[0] ?? null);
       if (!existing) return null;
 
@@ -1243,6 +1256,11 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
             : data.metadata;
       }
 
+      if ((["cwd", "sourceType", "repoUrl", "remoteProvider", "remoteWorkspaceRef", "sharedWorkspaceKey"] as const)
+        .some(key => patch[key] !== undefined && patch[key] !== existing[key])) {
+        await preserveConfiguredProjectWorkspaceRootProvenance(tx, existing);
+      }
+
       const updated = await db.transaction(async (tx) => {
         if (data.isPrimary === true) {
           await tx
@@ -1329,7 +1347,7 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
 
     removeWorkspace: async (projectId: string, workspaceId: string): Promise => db.transaction(async tx => {
       const db = tx as unknown as Db;
-      await db.select({ id: projects.id }).from(projects).where(eq(projects.id, projectId)).for("update");
+      if (!await lockProjectRootMutation(tx, projectId)) return null;
       const existing = await db
         .select()
         .from(projectWorkspaces)
@@ -1339,9 +1357,11 @@ export function projectService(db: Db, options: { captureWorkspaceUpdates?: bool
             eq(projectWorkspaces.projectId, projectId),
           ),
         )
+        .for("update")
         .then((rows) => rows[0] ?? null);
       if (!existing) return null;
 
+      await preserveConfiguredProjectWorkspaceRootProvenance(tx, existing);
       const removed = await db.transaction(async (tx) => {
         const row = await tx
           .delete(projectWorkspaces)
diff --git a/server/src/services/runtime-service-env.ts b/server/src/services/runtime-service-env.ts
new file mode 100644
index 00000000000..2deb8ee7645
--- /dev/null
+++ b/server/src/services/runtime-service-env.ts
@@ -0,0 +1,14 @@
+export function sanitizeRuntimeServiceBaseEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
+  const env: NodeJS.ProcessEnv = { ...baseEnv };
+  for (const key of Object.keys(env)) {
+    if (key.startsWith("PAPERCLIP_")) {
+      delete env[key];
+    }
+  }
+  delete env.BETTER_AUTH_URL;
+  delete env.BETTER_AUTH_BASE_URL;
+  delete env.DATABASE_URL;
+  delete env.npm_config_tailscale_auth;
+  delete env.npm_config_authenticated_private;
+  return env;
+}
diff --git a/server/src/services/task-workspace-selection.ts b/server/src/services/task-workspace-selection.ts
new file mode 100644
index 00000000000..c10e2f7e783
--- /dev/null
+++ b/server/src/services/task-workspace-selection.ts
@@ -0,0 +1,9 @@
+import { and, inArray, isNull } from "drizzle-orm";
+import { executionWorkspaces } from "@paperclipai/db";
+
+export function taskWorkspaceSelectableCondition() {
+  return and(
+    inArray(executionWorkspaces.status, ["active", "idle"]),
+    isNull(executionWorkspaces.closedAt),
+  )!;
+}
diff --git a/server/src/services/task-workspace-source-access.ts b/server/src/services/task-workspace-source-access.ts
new file mode 100644
index 00000000000..cf226305110
--- /dev/null
+++ b/server/src/services/task-workspace-source-access.ts
@@ -0,0 +1,62 @@
+import { executionWorkspaces, issues, type Db } from "@paperclipai/db";
+import { and, eq, inArray, or, sql } from "drizzle-orm";
+import { forbidden } from "../errors.js";
+import { accessService } from "./access.js";
+import { executionWorkspaceReadSqlCondition, type AuthorizationActor } from "./authorization.js";
+
+type DbTransaction = Parameters[0]>[0];
+type WorkspaceAccessContext = { write?: boolean; issueId?: string | null; parentIssueId?: string | null; assigneeAgentId?: string | null; assigneeUserId?: string | null };
+
+export async function assertTaskWorkspaceSourceProjectAccess(
+  db: Db | DbTransaction, actor: AuthorizationActor, companyId: string, projectId: string | null, options: WorkspaceAccessContext = {},
+) {
+  if (!projectId) return;
+  const decision = await accessService(db as Db).decide({
+    actor, action: "project:read", resource: { type: "project", companyId, projectId },
+  });
+  if (!decision.allowed) throw forbidden(decision.explanation);
+  if (options.write) {
+    const [task] = actor.type === "board" && options.issueId
+      ? await db.select({ assigneeAgentId: issues.assigneeAgentId, assigneeUserId: issues.assigneeUserId }).from(issues)
+        .where(and(eq(issues.id, options.issueId), eq(issues.companyId, companyId)))
+      : [];
+    const assigneeAgentId = actor.type === "agent" ? actor.agentId ?? null : task?.assigneeAgentId ?? options.assigneeAgentId ?? null;
+    const assigneeUserId = actor.type === "agent" ? null : task?.assigneeUserId ?? options.assigneeUserId ?? null;
+    const assignment = await accessService(db as Db).decide({
+      actor, action: "project:write_workspace",
+      resource: { type: "issue", companyId, projectId, assigneeAgentId, assigneeUserId,
+        issueId: options.issueId, parentIssueId: options.parentIssueId },
+      scope: { projectId, assigneeAgentId, assigneeUserId },
+    });
+    if (!assignment.allowed) throw forbidden(assignment.explanation);
+  }
+}
+
+export async function assertTaskWorkspaceAccess(
+  db: Db | DbTransaction, actor: AuthorizationActor, companyId: string, workspaceId: string, options: WorkspaceAccessContext = {},
+) {
+  const [workspace] = await db.select().from(executionWorkspaces).where(and(
+    eq(executionWorkspaces.id, workspaceId), eq(executionWorkspaces.companyId, companyId),
+    await executionWorkspaceReadSqlCondition(db, actor),
+  ));
+  if (!workspace) throw forbidden("Task workspace access is no longer available");
+  await assertTaskWorkspaceSourceProjectAccess(db, actor, companyId, workspace.projectId, { ...options, write: options.write && workspace.strategyType !== "git_worktree" });
+  const retainedIssueIds = Object.keys(workspace.metadata?._issuePrivacySources ?? {});
+  if (workspace.sourceIssueId) retainedIssueIds.push(workspace.sourceIssueId);
+  const sources = await db.select({ id: issues.id }).from(issues).where(and(
+    eq(issues.companyId, companyId),
+    or(eq(issues.executionWorkspaceId, workspaceId), retainedIssueIds.length > 0
+      ? inArray(sql`${issues.id}::text`, retainedIssueIds) : undefined),
+  ));
+  const sourceIds = new Set(sources.map(source => source.id));
+  if (retainedIssueIds.some(id => !sourceIds.has(id))) throw forbidden("Task workspace source is no longer accessible");
+  const access = accessService(db as Db);
+  if (sources.length === 0 && !workspace.projectId) {
+    const decision = await access.decide({ actor, action: "company_scope:read", resource: { type: "company", companyId } });
+    if (!decision.allowed) throw forbidden("An unscoped workspace is outside this actor's authorization boundary");
+  }
+  for (const source of sources) {
+    const decision = await access.decide({ actor, action: "issue:read", resource: { type: "issue", companyId, issueId: source.id } });
+    if (!decision.allowed) throw forbidden("Task workspace retains files outside this actor's authorization boundary");
+  }
+}
diff --git a/server/src/services/task-workspace-tools.ts b/server/src/services/task-workspace-tools.ts
new file mode 100644
index 00000000000..d2dfa72e463
--- /dev/null
+++ b/server/src/services/task-workspace-tools.ts
@@ -0,0 +1,36 @@
+import { prepareWorkspaceRepositorySchema, selectTaskWorkspaceSchema } from "@paperclipai/shared";
+import { badRequest } from "../errors.js";
+
+function repositoryView(repo: Record) {
+  return { id: repo.id, repoUrl: repo.repoUrl, relativePath: repo.relativePath, requestedRef: repo.requestedRef,
+    state: repo.state, pinnedCommit: repo.pinnedCommit, branchName: repo.branchName };
+}
+
+export async function callTaskWorkspaceTool(input: { name: string; arguments: Record; apiUrl: string; token: string; companyId: string; issueId: string; workspaceRoot?: string }) {
+  let route = `/issues/${input.issueId}/workspace`;
+  let method = "GET";
+  let body: unknown;
+  switch (input.name) {
+    case "get_workspace": break;
+    case "list_workspaces": route = `/companies/${input.companyId}/execution-workspaces?summary=true&selectableForTask=true`; break;
+    case "select_workspace": method = "PUT"; body = selectTaskWorkspaceSchema.parse(input.arguments); break;
+    case "prepare_repository": method = "POST"; route += "/repositories"; body = prepareWorkspaceRepositorySchema.parse(input.arguments); break;
+    default: throw badRequest("Unknown workspace tool");
+  }
+  const response = await fetch(`${input.apiUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/api${route}`, { method, headers: { Authorization: `Bearer ${input.token}`, "Content-Type": "application/json" }, ...(body ? { body: JSON.stringify(body) } : {}), signal: AbortSignal.timeout(60_000) });
+  const result = await response.json();
+  if (!response.ok) throw new Error(typeof result.error === "string" ? result.error : `Workspace tool failed (${response.status})`);
+  if (input.name === "prepare_repository") {
+    const { operationId, ...rest } = result;
+    return { ...rest, ...(result.repository ? { repository: repositoryView(result.repository) } : {}), preparationId: operationId };
+  }
+  if (input.name === "get_workspace") {
+    const workspace = result.workspace;
+    return { issueId: result.issueId, bindingRevision: result.bindingRevision, selection: result.selection,
+      pendingSelection: result.pendingSelection, cwd: input.workspaceRoot ?? null,
+      workspace: workspace ? { id: workspace.id, projectId: workspace.projectId, mode: workspace.mode, status: workspace.status } : null,
+      repositories: (result.repositories ?? []).map(repositoryView),
+      capabilities: result.capabilities, fileLocations: result.fileLocations };
+  }
+  return result;
+}
diff --git a/server/src/services/trust-preset-resolver.ts b/server/src/services/trust-preset-resolver.ts
index 7b0ab3b7f6b..7d7221ba84d 100644
--- a/server/src/services/trust-preset-resolver.ts
+++ b/server/src/services/trust-preset-resolver.ts
@@ -13,7 +13,7 @@ type JsonRecord = Record;
 
 export const LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH = 12;
 
-export type TrustPresetPolicySource = "agent" | "project" | "issue" | "run";
+export type TrustPresetPolicySource = "agent" | "project" | "workspace_source_project" | "issue" | "run";
 
 export type ResolveCoreTrustPresetInput = {
   companyId: string;
@@ -25,6 +25,10 @@ export type ResolveCoreTrustPresetInput = {
     companyId?: string | null;
     executionWorkspacePolicy?: unknown;
   } | null;
+  workspaceSourceProject?: {
+    companyId?: string | null;
+    executionWorkspacePolicy?: unknown;
+  } | null;
   issue?: {
     companyId?: string | null;
     executionPolicy?: unknown;
@@ -280,6 +284,12 @@ export function resolveCoreTrustPreset(input: ResolveCoreTrustPresetInput): Trus
   if ("kind" in project) return project;
   sources.push(project);
 
+  const workspaceSourcePolicy = asRecord(input.workspaceSourceProject?.executionWorkspacePolicy);
+  const workspaceSource = parseSource("workspace_source_project", input.workspaceSourceProject?.companyId,
+    workspaceSourcePolicy, workspaceSourcePolicy?.authorizationPolicy, sourcePresets);
+  if ("kind" in workspaceSource) return workspaceSource;
+  sources.push(workspaceSource);
+
   const issuePolicy = asRecord(input.issue?.executionPolicy);
   const issue = parseSource("issue", input.issue?.companyId, issuePolicy, issuePolicy?.authorizationPolicy, sourcePresets);
   if ("kind" in issue) return issue;
diff --git a/server/src/services/workspace-file-resources.ts b/server/src/services/workspace-file-resources.ts
index 9b57cfe3458..d7ec8c44dbd 100644
--- a/server/src/services/workspace-file-resources.ts
+++ b/server/src/services/workspace-file-resources.ts
@@ -1,5 +1,6 @@
 import { issueReadSqlCondition, executionWorkspaceReadSqlCondition, projectReadSqlCondition, type AuthorizationActor } from "./authorization.js";
 import fs from "node:fs/promises";
+import { assertExecutionWorkspaceRootReadAccess } from "./execution-workspace-repositories.js";
 import path from "node:path";
 import { and, desc, eq, inArray, isNull } from "drizzle-orm";
 import type { Db } from "@paperclipai/db";
@@ -113,6 +114,7 @@ type ExecutionWorkspaceRow = typeof executionWorkspaces.$inferSelect;
 type ProjectWorkspaceRow = typeof projectWorkspaces.$inferSelect;
 
 type WorkspaceCandidate = {
+  companyId: string;
   workspaceKind: WorkspaceFileWorkspaceKind;
   workspaceId: string;
   projectId?: string | null;
@@ -474,6 +476,7 @@ function candidateFromExecutionWorkspace(row: ExecutionWorkspaceRow): WorkspaceC
   const rootPath = row.cwd || row.providerRef || null;
   const remote = !["local_fs", "git_worktree"].includes(provider) || !rootPath || row.status !== "active" || row.closedAt != null;
   return {
+    companyId: row.companyId,
     workspaceKind: "execution_workspace",
     workspaceId: row.id,
     provider,
@@ -491,6 +494,7 @@ function candidateFromProjectWorkspace(
   const rootPath = row.cwd ?? null;
   const remote = !LOCAL_PROJECT_WORKSPACE_SOURCE_TYPES.has(row.sourceType) || !rootPath;
   return {
+    companyId: row.companyId,
     workspaceKind: "project_workspace",
     workspaceId: row.id,
     projectId: project?.id ?? row.projectId,
@@ -1039,7 +1043,7 @@ async function listChangedWorkspaceFiles(input: {
 }
 
 export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor) {
-  async function visibleCandidates(candidates: WorkspaceCandidate[]) {
+  async function visibleCandidates(candidates: WorkspaceCandidate[], skipDeniedSources = false) {
     if (!actor) return candidates;
     const visible: WorkspaceCandidate[] = [];
     for (const candidate of candidates) {
@@ -1049,7 +1053,25 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
         : await db.select({ id: projectWorkspaces.id }).from(projectWorkspaces)
           .innerJoin(projects, eq(projects.id, projectWorkspaces.projectId))
           .where(and(eq(projectWorkspaces.id, candidate.workspaceId), await projectReadSqlCondition(db, actor)));
-      if (rows.length) visible.push(candidate);
+      if (!rows.length) continue;
+      if (candidate.rootPath && (!candidate.remote || candidate.provider === "local_fs" || LOCAL_PROJECT_WORKSPACE_SOURCE_TYPES.has(candidate.provider))) {
+        try {
+          const canonicalCwd = await fs.realpath(candidate.rootPath);
+          await assertExecutionWorkspaceRootReadAccess(db, {
+            companyId: candidate.companyId, cwd: canonicalCwd, actor,
+            sourceWorkspaceId: candidate.workspaceKind === "execution_workspace" ? candidate.workspaceId : undefined,
+          });
+          candidate.rootPath = canonicalCwd;
+        } catch (error) {
+          if (isHttpStatus(error, 403) || isHttpStatus(error, 404)
+            || ["ENOENT", "ENOTDIR", "EACCES", "EPERM", "ELOOP", "ENAMETOOLONG"].includes((error as NodeJS.ErrnoException).code ?? "")) {
+            if (skipDeniedSources) continue;
+            throw notFound("Workspace file not found");
+          }
+          throw error;
+        }
+      }
+      visible.push(candidate);
     }
     return visible;
   }
@@ -1094,7 +1116,7 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
     if (explicitTarget) return [explicitTarget];
 
     const candidates: WorkspaceCandidate[] = [];
-    if ((selector === "auto" || selector === "execution") && issue.projectId) {
+    if (selector === "auto" || selector === "execution") {
       const executionIds = [issue.executionWorkspaceId].filter((id): id is string => Boolean(id));
       let executionRows: ExecutionWorkspaceRow[] = [];
       if (executionIds.length > 0) {
@@ -1110,7 +1132,6 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
         const activeRows = await db.select().from(executionWorkspaces).where(
           and(
             eq(executionWorkspaces.companyId, issue.companyId),
-            eq(executionWorkspaces.projectId, issue.projectId),
             inArray(executionWorkspaces.sourceIssueId, sourceIssueIds),
             eq(executionWorkspaces.status, "active"),
             isNull(executionWorkspaces.closedAt),
@@ -1126,30 +1147,29 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
       }
     }
 
-    if ((selector === "auto" || selector === "project") && issue.projectId) {
+    if ((selector === "auto" || selector === "project") && (issue.projectId || issue.projectWorkspaceId)) {
       if (issue.projectWorkspaceId) {
         const rows = await db.select().from(projectWorkspaces).where(
           and(
             eq(projectWorkspaces.companyId, issue.companyId),
-            eq(projectWorkspaces.projectId, issue.projectId),
             eq(projectWorkspaces.id, issue.projectWorkspaceId),
           ),
         ).limit(1);
         if (rows[0]) candidates.push(candidateFromProjectWorkspace(rows[0]));
       }
-      const primaryRows = await db.select().from(projectWorkspaces).where(
+      const primaryRows = issue.projectId ? await db.select().from(projectWorkspaces).where(
         and(
           eq(projectWorkspaces.companyId, issue.companyId),
           eq(projectWorkspaces.projectId, issue.projectId),
           eq(projectWorkspaces.isPrimary, true),
         ),
-      ).limit(1);
+      ).limit(1) : [];
       if (primaryRows[0] && !candidates.some((candidate) => candidate.workspaceId === primaryRows[0]!.id)) {
         candidates.push(candidateFromProjectWorkspace(primaryRows[0]));
       }
     }
 
-    return visibleCandidates(candidates);
+    return visibleCandidates(candidates, selector === "auto");
   }
 
   async function loadAvailabilityTargets(
@@ -1189,8 +1209,13 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
           error: unprocessable("Workspace does not belong to the selected project", { code: "workspace_project_mismatch" }),
         });
       } else {
-        const [candidate] = await visibleCandidates([candidateFromProjectWorkspace(workspace, { id: project.id, name: project.name })]);
-        targets.set(targetKey, candidate ? { candidate } : { error: notFound("Project workspace not found") });
+        try {
+          const [candidate] = await visibleCandidates([candidateFromProjectWorkspace(workspace, { id: project.id, name: project.name })]);
+          targets.set(targetKey, candidate ? { candidate } : { error: notFound("Project workspace not found") });
+        } catch (error) {
+          if (!(error instanceof HttpError) || !expectedAvailabilityReason(error)) throw error;
+          targets.set(targetKey, { error });
+        }
       }
     }
     return targets;
@@ -1222,7 +1247,7 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
       seen.add(row.workspace.id);
       candidates.push(candidateFromProjectWorkspace(row.workspace, row.project));
     }
-    return visibleCandidates(candidates);
+    return visibleCandidates(candidates, true);
   }
 
   async function discoverUniqueProjectWorkspaceMatch(
@@ -1270,7 +1295,17 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
     const untargetedQueries = queries.filter(
       (item) => !item.unavailableReason && !item.query.projectId && !item.query.workspaceId,
     );
-    const initialCandidates = untargetedQueries.length > 0 ? await listCandidates(issue, "auto") : [];
+    const candidatesBySelector = new Map();
+    const selectorErrors = new Map();
+    for (const selector of new Set(untargetedQueries.map(item => item.query.workspace))) {
+      try {
+        candidatesBySelector.set(selector, await listCandidates(issue, selector));
+      } catch (error) {
+        if (!expectedAvailabilityReason(error)) throw error;
+        selectorErrors.set(selector, error);
+      }
+    }
+    const initialCandidates = candidatesBySelector.get("auto") ?? [];
     const needsDiscovery = initialCandidates.some((candidate) => !candidate.remote)
       && untargetedQueries.some((item) => item.query.workspace === "auto");
     const discoveryCandidates = needsDiscovery
@@ -1291,13 +1326,10 @@ export function workspaceFileResourceService(db: Db, actor?: AuthorizationActor)
             : null;
           if (explicitTarget?.error) throw explicitTarget.error;
 
+          if (!explicitTarget && selectorErrors.has(item.query.workspace)) throw selectorErrors.get(item.query.workspace);
           const candidates = explicitTarget?.candidate
             ? [explicitTarget.candidate]
-            : initialCandidates.filter((candidate) => {
-                if (item.query.workspace === "execution") return candidate.workspaceKind === "execution_workspace";
-                if (item.query.workspace === "project") return candidate.workspaceKind === "project_workspace";
-                return true;
-              });
+            : candidatesBySelector.get(item.query.workspace) ?? [];
           if (candidates.length === 0) {
             throw unprocessable("No workspace is available for this issue", { code: "no_workspace" });
           }
diff --git a/server/src/services/workspace-host-provision.ts b/server/src/services/workspace-host-provision.ts
new file mode 100644
index 00000000000..f5a7d07cd45
--- /dev/null
+++ b/server/src/services/workspace-host-provision.ts
@@ -0,0 +1,32 @@
+import { projectWorkspaces, type Db } from "@paperclipai/db";
+import type { TaskWorkspaceIntent } from "@paperclipai/shared";
+import { and, eq } from "drizzle-orm";
+
+import { resolveProjectDefaultWorkspaceId } from "./project-default-workspace.js";
+
+export const CONTROLLER_PROVISION_COPY_ONLY = "_controllerProvisionCopyOnly";
+
+export async function workspaceRequiresCopyOnlyProvisioning(db: Db | Parameters[0]>[0], input: {
+  companyId: string;
+  task: { id: string; projectId: string | null; workspaceSelection?: TaskWorkspaceIntent | null } | null;
+  sourceProjectId: string | null;
+  sourceWorkspaceId: string | null;
+  boundWorkspace?: { sourceIssueId?: string | null; metadata?: Record | null } | null;
+}) {
+  if (input.boundWorkspace?.metadata?.[CONTROLLER_PROVISION_COPY_ONLY] === true) return true;
+  const task = input.task;
+  if (!task) return Boolean(input.sourceProjectId || input.sourceWorkspaceId);
+  const intent = task.workspaceSelection;
+  if (intent && !["project", "operator_cwd", "task_default"].includes(intent.source)) return true;
+  if (intent?.selection.kind === "existing") return true;
+  if (input.boundWorkspace?.sourceIssueId && input.boundWorkspace.sourceIssueId !== task.id) return true;
+  if (intent?.source === "operator_cwd" && !input.sourceProjectId && !input.sourceWorkspaceId) return false;
+  if (!input.sourceProjectId || input.sourceProjectId !== task.projectId || !input.sourceWorkspaceId) return true;
+  const defaultSourceId = await resolveProjectDefaultWorkspaceId(db, {
+    companyId: input.companyId, projectId: input.sourceProjectId,
+  });
+  if (defaultSourceId !== input.sourceWorkspaceId) return true;
+  const [source] = await db.select({ id: projectWorkspaces.id }).from(projectWorkspaces)
+    .where(and(eq(projectWorkspaces.companyId, input.companyId), eq(projectWorkspaces.projectId, input.sourceProjectId), eq(projectWorkspaces.id, defaultSourceId))).limit(1);
+  return !source;
+}
diff --git a/server/src/services/workspace-runtime.ts b/server/src/services/workspace-runtime.ts
index 74182c1705b..35fe6657772 100644
--- a/server/src/services/workspace-runtime.ts
+++ b/server/src/services/workspace-runtime.ts
@@ -1,3 +1,5 @@
+import { sanitizeRuntimeServiceBaseEnv } from "./runtime-service-env.js";
+export { sanitizeRuntimeServiceBaseEnv } from "./runtime-service-env.js";
 import { execFile, spawn, type ChildProcess } from "node:child_process";
 import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
 import fs from "node:fs/promises";
@@ -681,23 +683,6 @@ export async function ensureServerWorkspaceLinksCurrent(
   );
 }
 
-export function sanitizeRuntimeServiceBaseEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
-  const env: NodeJS.ProcessEnv = { ...baseEnv };
-  for (const key of Object.keys(env)) {
-    if (key.startsWith("PAPERCLIP_")) {
-      delete env[key];
-    }
-  }
-  // These origin settings belong to the parent instance. Letting them leak into a
-  // managed worktree runtime can send auth cookies and OAuth callbacks to the wrong
-  // Paperclip instance. Runtime/service overrides are merged back after sanitizing.
-  delete env.BETTER_AUTH_URL;
-  delete env.BETTER_AUTH_BASE_URL;
-  delete env.DATABASE_URL;
-  delete env.npm_config_tailscale_auth;
-  delete env.npm_config_authenticated_private;
-  return env;
-}
 
 function stableRuntimeServiceId(input: {
   adapterType: string;
@@ -2717,10 +2702,17 @@ function parseGitWorktreeListPorcelain(raw: string): GitWorktreeListEntry[] {
 }
 
 async function resolveGitOwnerRepoRoot(cwd: string): Promise {
-  const checkoutRoot = path.resolve(await runGit(["rev-parse", "--show-toplevel"], cwd));
-  const commonDir = await runGit(["rev-parse", "--git-common-dir"], checkoutRoot).catch(() => null);
-  if (!commonDir) return checkoutRoot;
-  return path.dirname(path.resolve(checkoutRoot, commonDir));
+  const checkoutRoot = await fs.realpath(path.resolve(await runGit(["rev-parse", "--show-toplevel"], cwd)));
+  try {
+    const commonDir = await fs.realpath(path.resolve(checkoutRoot, await runGit(["rev-parse", "--git-common-dir"], checkoutRoot)));
+    const candidate = path.dirname(commonDir);
+    const candidateRoot = await fs.realpath(path.resolve(await runGit(["rev-parse", "--show-toplevel"], candidate)));
+    const candidateGitDir = await fs.realpath(path.resolve(candidate, await runGit(["rev-parse", "--absolute-git-dir"], candidate)));
+    const candidateCommonDir = await fs.realpath(path.resolve(candidate, await runGit(["rev-parse", "--git-common-dir"], candidate)));
+    if (candidateRoot === candidate && candidateGitDir === commonDir && candidateCommonDir === commonDir) return candidateRoot;
+  } catch {
+  }
+  return checkoutRoot;
 }
 
 async function findRegisteredGitWorktreeByBranch(repoRoot: string, branchName: string): Promise {
@@ -3242,8 +3234,10 @@ async function provisionExecutionWorktree(input: {
   issue: ExecutionWorkspaceIssueRef | null;
   agent: ExecutionWorkspaceAgentRef;
   created: boolean;
+  skipHostProvision?: boolean;
   recorder?: WorkspaceOperationRecorder | null;
 }) {
+  if (input.skipHostProvision) return;
   const provisionCommand = resolveWorkspaceProvisionCommand(input.strategy, input.repoRoot);
   if (!provisionCommand) return;
   const resolvedProvisionCommand = resolveRepoManagedWorkspaceCommand(provisionCommand, input.repoRoot);
@@ -3338,6 +3332,9 @@ export async function realizeExecutionWorkspace(input: {
   enableWorkspaceDirtyQuarantineRepair?: boolean;
   recorder?: WorkspaceOperationRecorder | null;
   resolveGitAuth?: GitRemoteAuthProvider | null;
+  skipHostProvision?: boolean;
+  beforeSourceCopy?: (sourceCwd: string, targetCwd: string) => Promise;
+  afterSourceCopy?: (sourceCwd: string, targetCwd: string) => Promise;
 }): Promise {
   const rawStrategy = parseObject(input.config.workspaceStrategy);
   const strategyType = asString(rawStrategy.type, "project_primary");
@@ -3419,6 +3416,7 @@ export async function realizeExecutionWorkspace(input: {
       },
     );
   }
+  await input.beforeSourceCopy?.(repoRoot, worktreePath);
   let pendingForwardBranchReconcile: PendingForwardBranchReconcile | null = null;
   const configuredBaseRef = typeof rawStrategy.baseRef === "string" && rawStrategy.baseRef.length > 0
     ? rawStrategy.baseRef
@@ -3441,6 +3439,7 @@ export async function realizeExecutionWorkspace(input: {
   await fs.mkdir(worktreeParentDir, { recursive: true });
 
   async function reuseExistingWorktree(reusablePath: string, effectiveBranchName = branchName, extraWarnings: string[] = []) {
+    if (reusablePath !== worktreePath) await input.beforeSourceCopy?.(repoRoot, reusablePath);
     // An exact-branch attach must never move the requested branch, so skip
     // the unstarted-worktree fast-forward that template-derived reuse gets.
     const refresh = currentBaseRefSha && !requestedExistingBranch
@@ -3482,7 +3481,9 @@ export async function realizeExecutionWorkspace(input: {
         }),
       });
     }
+    await input.afterSourceCopy?.(repoRoot, reusablePath);
     await provisionExecutionWorktree({
+      skipHostProvision: input.skipHostProvision,
       strategy: rawStrategy,
       base: input.base,
       repoRoot,
@@ -3640,7 +3641,9 @@ export async function realizeExecutionWorkspace(input: {
         },
       );
     }
+    await input.afterSourceCopy?.(repoRoot, worktreePath);
     await provisionExecutionWorktree({
+      skipHostProvision: input.skipHostProvision,
       strategy: rawStrategy,
       base: input.base,
       repoRoot,
@@ -3735,7 +3738,9 @@ export async function realizeExecutionWorkspace(input: {
       return await reuseExistingWorktree(reusablePath);
     }
   }
+  await input.afterSourceCopy?.(repoRoot, worktreePath);
   await provisionExecutionWorktree({
+    skipHostProvision: input.skipHostProvision,
     strategy: rawStrategy,
     base: input.base,
     repoRoot,
@@ -3788,6 +3793,9 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: {
   enableWorkspaceDirtyQuarantineRepair?: boolean;
   recorder?: WorkspaceOperationRecorder | null;
   resolveGitAuth?: GitRemoteAuthProvider | null;
+  skipHostProvision?: boolean;
+  beforeSourceCopy?: (sourceCwd: string, targetCwd: string) => Promise;
+  afterSourceCopy?: (sourceCwd: string, targetCwd: string) => Promise;
 }): Promise {
   const cwd = asString(input.workspace.cwd ?? input.workspace.providerRef, "").trim();
   if (!cwd) return null;
@@ -3796,8 +3804,8 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: {
   const realized: RealizedExecutionWorkspace = {
     baseCwd: input.base.baseCwd,
     source: input.workspace.mode === "shared_workspace" ? "project_primary" : "task_session",
-    projectId: input.workspace.projectId ?? input.base.projectId,
-    workspaceId: input.workspace.projectWorkspaceId ?? input.base.workspaceId,
+    projectId: input.workspace.projectId === undefined ? input.base.projectId : input.workspace.projectId,
+    workspaceId: input.workspace.projectWorkspaceId === undefined ? input.base.workspaceId : input.workspace.projectWorkspaceId,
     repoUrl: input.workspace.repoUrl ?? input.base.repoUrl,
     repoRef: input.workspace.baseRef ?? input.base.repoRef,
     additionalWorkspaces: input.base.additionalWorkspaces ?? [],
@@ -3840,6 +3848,7 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: {
     );
   }
   const repoRoot = await runGit(["rev-parse", "--show-toplevel"], baseCwd);
+  await input.beforeSourceCopy?.(repoRoot, realized.worktreePath ?? cwd);
   const recordedBaseRefSha = readRecordedBaseRefSha(input.workspace.metadata);
   if (await directoryExists(cwd)) {
     const reuseBaseRef = input.workspace.baseRef ?? input.base.repoRef ?? null;
@@ -3918,7 +3927,9 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: {
     });
     realized.warnings = [...repairWarnings, ...baseRefreshWarnings, ...baseDrift.warnings];
     realized.baseRefSha = refresh.baseRefSha ?? recordedBaseRefSha ?? baseDrift.branchBaseRefSha ?? baseDrift.currentBaseRefSha;
+    await input.afterSourceCopy?.(repoRoot, realized.worktreePath ?? cwd);
     await provisionExecutionWorktree({
+      skipHostProvision: input.skipHostProvision,
       strategy: {
         type: "git_worktree",
         ...(provisionCommand ? { provisionCommand } : {}),
@@ -4010,7 +4021,9 @@ export async function ensurePersistedExecutionWorkspaceAvailable(input: {
     skipRefresh: true,
   });
 
+  await input.afterSourceCopy?.(repoRoot, worktreePath);
   await provisionExecutionWorktree({
+    skipHostProvision: input.skipHostProvision,
     strategy: {
       type: "git_worktree",
       ...(provisionCommand ? { provisionCommand } : {}),
@@ -7435,6 +7448,7 @@ type EnsureRuntimeServicesForRunInput = {
   workspace: RealizedExecutionWorkspace;
   executionWorkspaceId?: string | null;
   config: Record;
+  skipHostProvision?: boolean;
   adapterEnv: Record;
   onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise;
   recorder?: WorkspaceOperationRecorder | null;
@@ -7451,7 +7465,9 @@ async function ensureRuntimeServicesForRunInvocation(
   });
   const acquiredServiceIds: string[] = [];
   const refs: RuntimeServiceRef[] = [];
-  const runtimeProvision = resolveRuntimeProvision(input);
+  const runtimeProvision = input.skipHostProvision
+    ? { command: "", kind: null }
+    : resolveRuntimeProvision(input);
   const runtimeProvisionCommand = runtimeProvision.command;
   const provisionCoordinator = createRuntimeProvisionCoordinator();
   const allowFixedPortFallback = await isPersistedIsolatedExecutionWorkspace({
diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md
index c3b6993bfd0..a4f0f95dd06 100644
--- a/skills/paperclip/SKILL.md
+++ b/skills/paperclip/SKILL.md
@@ -544,6 +544,12 @@ Routines are recurring tasks. Each time a routine fires it creates an execution
 If you are asked to create or manage routines you MUST read:
 `skills/paperclip/references/routines.md`
 
+## Task Files and Workspace Choices
+
+Use the task's provided working directory for task files. `AGENT_HOME` holds personal instructions and memory; provider home is runtime-managed. A task does not need a project or Git repository. Do not clone unrelated repositories or place task output in home to compensate for a missing project.
+
+Use `get_workspace` to inspect the current folder, pending selection, and capabilities. Use `list_workspaces` for authorized existing folders and `list_project_repositories` for repository choices. `select_workspace` and `prepare_repository` express intent; the harness validates and prepares it. Respect `next_normal_admission` responses: keep the current root, do not claim the requested checkout exists, and do not retry in a loop or interrupt the run. Project assignment alone does not relocate files. See `references/issue-workspaces.md` for the API equivalents.
+
 ## Issue Workspace Runtime Controls
 
 When an issue needs browser/manual QA or a preview server, inspect its current execution workspace and use Paperclip's workspace runtime controls instead of starting unmanaged background servers yourself.
diff --git a/skills/paperclip/references/issue-workspaces.md b/skills/paperclip/references/issue-workspaces.md
index 41f5e62c9b7..6442910537d 100644
--- a/skills/paperclip/references/issue-workspaces.md
+++ b/skills/paperclip/references/issue-workspaces.md
@@ -1,6 +1,6 @@
 # Issue Workspace Runtime Controls
 
-Use this reference when an issue has an isolated execution workspace and you need to inspect or run that workspace's services, especially for QA/browser verification.
+Use this reference to choose task file placement or inspect and run workspace services. A workspace may be a plain task directory without a project or Git repository.
 
 ## Discover the Workspace
 
@@ -20,6 +20,20 @@ Read `currentExecutionWorkspace`:
 
 If `currentExecutionWorkspace` is `null`, the issue does not currently have a realized execution workspace. For child/follow-up work, create the child with `parentId` or use `inheritExecutionWorkspaceFromIssueId` so Paperclip preserves workspace continuity.
 
+## Choose Files and Repositories
+
+`GET /api/issues/:issueId/workspace` returns the current binding, revision, pending selection, repository inventory, and available preparation capabilities. The Runner equivalents are `get_workspace` and `list_workspaces`.
+
+`PUT /api/issues/:issueId/workspace` accepts a stable `requestKey`, the inspected `expectedBindingRevision`, and one typed `selection`:
+
+- `{ "kind": "task_directory" }`: the task's separate directory.
+- `{ "kind": "existing", "workspaceId": "" }`: intentionally reuse a workspace.
+- `{ "kind": "configured_source", "projectWorkspaceId": "", "mode": "shared" }`: use its configured folder; `managed_isolated` requests an isolated checkout.
+
+`POST /api/issues/:issueId/workspace/repositories` accepts a stable `requestKey`, optional `ref`, and `repository: { kind: "catalog", id: "" }`. Discover IDs with `list_project_repositories`. Existing public HTTPS GitHub URLs can use `{ kind: "url", url: "https://github.com/owner/repo" }`; URLs do not grant private credentials or create a remote repository.
+
+Root selections and repository preparation return `next_normal_admission`. The active process retains its root. Do not claim a queued checkout is ready or repeatedly request it. A different ref cannot silently reset an existing checkout. New noncoding work can proceed immediately in task files without any repository. Explicitly shared folders remain shared; do not remove them when finishing one task.
+
 ## Control Services
 
 Prefer Paperclip-managed runtime service controls over manual `pnpm dev &` or ad-hoc background processes. These endpoints keep service state, URLs, logs, and ownership visible to other agents and the board.
diff --git a/tests/runner-e2e/FIXTURES.md b/tests/runner-e2e/FIXTURES.md
index 2e94cf78c4a..05dbdb66b4f 100644
--- a/tests/runner-e2e/FIXTURES.md
+++ b/tests/runner-e2e/FIXTURES.md
@@ -853,3 +853,21 @@ not task success or operator cancellation. Stop during an unresolved permission
 remains a separate `native-active-stop/pending-permission-stop` gate.
 
 Pi controls definition 9 and native definition 16 create an explicit title through the production search creation action and bind the task ID from the public creation response. The scoped task and assignee must match before any native control. Automatic naming is outside these strict native-operation fixtures; all existing permission, byte, process, run-count and cleanup assertions remain required. Preserve the failed title-lookup attempt as its original failure.
+
+## Projectless task files and repository admission
+
+The explicit `task_workspaces` flow supplies no project and strips the legacy
+profile's operator cwd. It retains the ordinary company, encrypted credentials,
+environment and agent fixtures. All mutations use production browser/API/semantic
+surfaces; the oracle never inserts database rows or prepares a replacement checkout.
+The passive public Git source is `https://github.com/octocat/Hello-World`.
+No GitHub credentials, remote repository creation or push is used.
+
+Three user-requested turns test pending idempotent preparation, controller restart,
+next-admission realization and later nested Git/dirty-file persistence. The source
+pin is observed from the real checkout rather than assumed from a mutable branch.
+Host Git inspection is limited to the verified isolated company/task root. Public
+execution-workspace file reads and run-attributed artifact downloads verify bytes.
+Calibration rejects wrong roots, missing history, changed pins, duplicate receipts,
+extra wakes, incomplete persistence, tool bypass and missing remote leases. Partial
+observations remain in retained attempt evidence; missing evidence cannot pass.
diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md
index 1e52a3b9d61..8ec26e4befc 100644
--- a/tests/runner-e2e/README.md
+++ b/tests/runner-e2e/README.md
@@ -2178,6 +2178,48 @@ always preserves recovery state regardless of this optional diagnostic flag.
 
 The `file-edit-validate` fixture independently downloads the exact active artifact work product from the tested run. It requires the matching run-attributed attachment, filename, MIME type, recorded byte count and SHA-256, then compares the downloaded content to the expected bytes. A workspace file alone cannot satisfy this gate. Explicit failed-case diagnostic retention follows the final result after integrity, isolation and evidence checks, including incomplete publication.
 
+## Projectless task workspaces (explicit-only)
+
+`task-workspaces` contains exactly three cells: legacy OpenCode locally and native
+OpenCode locally and on Daytona, using the existing qualified model. These are
+configured acceptance cells; registration and fixture calibration do not establish
+live qualification.
+
+```sh
+pnpm test:e2e:runner -- --list --suite task-workspaces
+pnpm test:e2e:runner -- --id task-workspaces.legacy-opencode.local.task-directory-repository-resume --max-automatic-retries 0
+pnpm test:e2e:runner -- --id task-workspaces.runner-opencode.local.task-directory-repository-resume --max-automatic-retries 0
+pnpm test:e2e:runner -- --id task-workspaces.runner-opencode.daytona.task-directory-repository-resume --max-automatic-retries 0
+```
+
+Each cell creates a task through the browser without a project or configured
+adapter cwd. The first turn writes task files and requests the same public
+repository preparation twice. Independent APIs must show one pending receipt
+and the same original root. After a controller restart, the second admission
+prepares the repository; the agent makes a local commit and leaves a dirty file.
+The third turn copies those prior bytes into a registered downloadable artifact.
+The oracle checks the task's exact root and stable binding, public file reads,
+repository pin, local Git ancestry and dirty bytes, artifact metadata/digest,
+three attributed successful runs, durable native terminal events and remote
+lease attribution. Daytona uses per-turn leases, so a warm process cannot stand
+in for persisted files. This suite does not qualify channel-default configuration
+or arbitrary repository credentials.
+
+These cells exercise trusted app-created tasks. Low-trust chat tasks cannot use
+`prepare_repository` to initiate controller clones, even for public URLs with
+full-auto provider permissions. Their pending acquisitions fail after a trust
+downgrade; verified existing public checkouts can be reused without acquisition.
+No-repository admission and immutable recovery remain supported. Trusted
+acquisition does not yet impose repository byte or cumulative storage quotas.
+
+Each cell allows three turns, one attempt, four minutes per turn and fifteen
+minutes total, with 1,000-cent company and agent budget hard stops. `--all` excludes
+it. `OPENROUTER_API_KEY` is required; Daytona also requires `DAYTONA_API_KEY`, a
+verified immutable image and its matching remote runner/provider artifacts.
+Use the normal isolated-instance cleanup and report pipeline. Private evidence
+includes `snapshots/task-workspaces.json` and `task-workspaces-runs.json`; only
+marked synthetic task screenshots can enter the public projection.
+
 ## Repository skill packages
 
 The explicit `repository-skills` suite imports the private
diff --git a/tests/runner-e2e/SECURITY.md b/tests/runner-e2e/SECURITY.md
index a5c17bb800a..b20d625c286 100644
--- a/tests/runner-e2e/SECURITY.md
+++ b/tests/runner-e2e/SECURITY.md
@@ -342,3 +342,14 @@ descriptor launches with synthetic owned processes and no provider credentials;
 it does not qualify paid Pi behavior or change previous failed grades.
 
 The private resource-admission marker and raw cleanup results control recovery-state retention independently of evidence packaging. A worker crash after admission keeps the owner-only recovery database; a confirmed pre-allocation bootstrap failure does not. Neither the marker nor the database enters published evidence.
+
+## Task workspace fixture source
+
+The explicit task-workspaces cells fetch the existing public octocat/Hello-World
+repository through normal anonymous repository preparation. They do not clone the
+Paperclip checkout, copy its Git credentials, create remote repositories or push.
+The agent's synthetic commit remains in the disposable task workspace. Independent
+Git reads validate that workspace's canonical path is inside the isolated instance
+before examining history. The existing credential redactor, private evidence
+packager and task-only screenshot allowlist remain authoritative. Each cell has a
+single attempt, three requested turns and company/agent budget hard stops.
diff --git a/tests/runner-e2e/catalog.test.ts b/tests/runner-e2e/catalog.test.ts
index d3393eb16dc..d0344178d2d 100644
--- a/tests/runner-e2e/catalog.test.ts
+++ b/tests/runner-e2e/catalog.test.ts
@@ -159,10 +159,10 @@ describe("runner E2E catalog", () => {
     expect(localIntegrityTasks).toHaveLength(2);
     expect(openRouterBreadthTasks).toHaveLength(3);
     expect(runnerSuites.map((suite) => suite.expectedMatrixSize)).toEqual([
-      4, 8, 10, 4, 4, 6, 63, 12, 6, 2, 30, 3, 16, 16, 2, 6, 8, 46, 2, 23, 15, 52, 6, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1, 116, 2,
+      3, 4, 8, 10, 4, 4, 6, 63, 12, 6, 2, 30, 3, 16, 16, 2, 6, 8, 46, 2, 23, 15, 52, 6, 6, 20, 26, 52, 28, 18, 2, 6, 6, 12, 10, 48, 16, 10, 2, 1, 1, 116, 2,
     ]);
-    expect(validateRunnerCatalog()).toHaveLength(726);
-    expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(726);
+    expect(validateRunnerCatalog()).toHaveLength(729);
+    expect(new Set(runnerMatrix.map((entry) => entry.id)).size).toBe(729);
     expect(
       runnerMatrix.filter((entry) => entry.suite.id === "core-compatibility"),
     ).toHaveLength(48);
diff --git a/tests/runner-e2e/catalog.ts b/tests/runner-e2e/catalog.ts
index 575401d1901..23a7a2b5417 100644
--- a/tests/runner-e2e/catalog.ts
+++ b/tests/runner-e2e/catalog.ts
@@ -1,3 +1,4 @@
+import { TASK_WORKSPACES_SUITE, taskWorkspaceTask, taskWorkspaceProfile, taskWorkspaceDefinitionDigest } from "./task-workspaces-cases.js";
 import { nativeActiveStopTasks } from "./native-active-stop-tasks.js";
 import { piControlTasks } from "./pi-controls-cases.js";
 import { cursorNativeTasks } from "./cursor-native-cases.js";
@@ -1104,6 +1105,16 @@ export const extendedHarnessFileTask: RunnerTaskFixture = {
 };
 
 export const runnerSuites: readonly RunnerSuiteFixture[] = [
+  {
+    id: TASK_WORKSPACES_SUITE, label: "Task workspaces without projects", manualOnly: true,
+    description: "Three browser-driven admissions prove deferred repository preparation, stable projectless task files and nested Git persistence across a controller restart.",
+    groups: [], profiles: runnerProfiles.filter(profile => ["legacy-opencode", "runner-opencode"].includes(profile.id)).map(taskWorkspaceProfile),
+    environments: runnerEnvironments, tasks: [taskWorkspaceTask],
+    excludedExecutionIds: ["task-workspaces.legacy-opencode.daytona.task-directory-repository-resume"],
+    expectedMatrixSize: 3,
+    definitionMetadata: { version: 1, definitionDigest: taskWorkspaceDefinitionDigest(), scheduling: "explicit-only", providerTurns: 3,
+      attempts: 1, budgetMonthlyCents: 1000, project: "none", cwd: "unconfigured-task-default", oracle: "public-file-download-and-host-nested-git", remoteLifecycle: "per_turn" },
+  },
   {
     id: "pi-controls", label: "Pi active controls", manualOnly: true,
     description: "Pending native-write Stop and browser-originated same-turn steering, with exact control receipts and independent retirement/no-effect evidence.",
diff --git a/tests/runner-e2e/live-fixtures.ts b/tests/runner-e2e/live-fixtures.ts
index 1a8bd126d28..753e18762c4 100644
--- a/tests/runner-e2e/live-fixtures.ts
+++ b/tests/runner-e2e/live-fixtures.ts
@@ -142,7 +142,7 @@ export async function setupLiveFixtures(input: {
         budgetMonthlyCents: ["native-completion", "native-instruction-consolidation", "native-connection-guidance"].includes(execution.suite.id)
           || (execution.suite.id === "everyday-workflows" && ["hire-reuse", "delegate-feedback"].includes(execution.task.id)) ? NATIVE_COMPLETION_BUDGET_CENTS
           : execution.suite.id === "task-titles" ? TASK_TITLE_BUDGET_CENTS
-          : execution.suite.id === "stock-harness" ? 1_000 : 0,
+          : ["stock-harness", "task-workspaces"].includes(execution.suite.id) ? 1_000 : 0,
       });
     },
     async teardown() {
diff --git a/tests/runner-e2e/pi-controls-evidence.test.ts b/tests/runner-e2e/pi-controls-evidence.test.ts
index 7be005c69ac..23f45210d8a 100644
--- a/tests/runner-e2e/pi-controls-evidence.test.ts
+++ b/tests/runner-e2e/pi-controls-evidence.test.ts
@@ -180,7 +180,7 @@ describe("Pi controls catalog admission", () => {
     expect(cells).toHaveLength(4); expect(cells.every(c => c.profile.qualificationCandidate === "pi" && c.task.expectedRunCount === 1)).toBe(true);
     expect(cells.map(c => `${c.environment.id}/${c.task.id}`).sort()).toEqual(["daytona/pending-permission-stop", "daytona/same-turn-steering", "local/pending-permission-stop", "local/same-turn-steering"]);
     expect(selectRunnerExecutions(parseRunnerSelectors(["--all"])).some(c => c.suite.id === "pi-controls")).toBe(false);
-    expect(validateRunnerCatalog()).toHaveLength(726);
+    expect(validateRunnerCatalog()).toHaveLength(729);
     for (const cell of cells) expect(buildRunnerE2EProcessEnvironment({}, [cell]).PAPERCLIP_RUNNER_ACPX_QUALIFICATION).toBeUndefined();
     expect(() => assertRemoteNativeEvidencePrerequisites(cells, {})).toThrow();
   });
diff --git a/tests/runner-e2e/registered-artifact.test.ts b/tests/runner-e2e/registered-artifact.test.ts
index ea3ca01fd01..b0754d56fb1 100644
--- a/tests/runner-e2e/registered-artifact.test.ts
+++ b/tests/runner-e2e/registered-artifact.test.ts
@@ -13,18 +13,37 @@ function fixture() {
   return { product, attachment, download, get, api: { get, request: { get: download } } as unknown as RunnerApi };
 }
 
-it("downloads the exact registered run-attributed output through its public path", async () => {
-  const { api, download } = fixture();
+it.each(["active", "ready_for_review", "approved"])("downloads the exact registered run-attributed %s output through its public path", async status => {
+  const { api, product, download } = fixture();
+  product.status = status;
   const artifacts = await readRegisteredArtifacts(api, "issue", "run", ["proof.txt"]);
   expect(download).toHaveBeenCalledWith("/api/attachments/attachment/content?download=1");
   expect((await evaluateMatcher({ kind: "artifact_exact", name: "proof.txt", expected: "verified\n", mimeType: "text/plain" }, { artifacts })).passed).toBe(true);
 });
 
-it.each(["unregistered", "old-run", "unattributed", "hash-mismatch", "download-failure"])("refuses %s output evidence", async reason => {
+it.each(["draft", "changes_requested", "merged", "closed", "failed", "archived", "superseded", "withdrawn", "unknown"])("refuses %s artifact registrations before downloading", async status => {
+  const { api, product, download } = fixture();
+  product.status = status;
+  await expect(readRegisteredArtifacts(api, "issue", "run", ["proof.txt"])).rejects.toThrow("Expected one registered artifact");
+  expect(download).not.toHaveBeenCalled();
+});
+
+it.each(["unregistered", "duplicate", "old-run", "unattributed", "wrong-issue", "wrong-attachment-issue", "wrong-type", "wrong-title", "wrong-filename", "wrong-attachment-id", "negative-size", "excessive-size", "size-mismatch", "hash-mismatch", "download-failure"])("refuses %s output evidence even when ready for review", async reason => {
   const { api, product, attachment, get, download } = fixture();
+  product.status = "ready_for_review";
   if (reason === "unregistered") get.mockImplementation(async (path: string) => path.endsWith("work-products") ? [] : [attachment]);
+  if (reason === "duplicate") get.mockImplementation(async (path: string) => path.endsWith("work-products") ? [product, { ...product, id: "duplicate", status: "approved" }] : [attachment]);
   if (reason === "old-run") product.createdByRunId = "other-run";
   if (reason === "unattributed") attachment.originatingRunId = "other-run";
+  if (reason === "wrong-issue") product.issueId = "other-issue";
+  if (reason === "wrong-attachment-issue") attachment.issueId = "other-issue";
+  if (reason === "wrong-type") product.type = "document";
+  if (reason === "wrong-title") product.title = "other.txt";
+  if (reason === "wrong-filename") attachment.originalFilename = "other.txt";
+  if (reason === "wrong-attachment-id") product.metadata.attachmentId = "other-attachment";
+  if (reason === "negative-size") attachment.byteSize = -1;
+  if (reason === "excessive-size") attachment.byteSize = 262_145;
+  if (reason === "size-mismatch") attachment.byteSize += 1;
   if (reason === "hash-mismatch") attachment.sha256 = "incorrect";
   if (reason === "download-failure") download.mockResolvedValue({ ok: () => false, status: () => 404 });
   await expect(readRegisteredArtifacts(api, "issue", "run", ["proof.txt"])).rejects.toThrow();
diff --git a/tests/runner-e2e/registered-artifact.ts b/tests/runner-e2e/registered-artifact.ts
index f0e4fc3d662..33b4df8b8b4 100644
--- a/tests/runner-e2e/registered-artifact.ts
+++ b/tests/runner-e2e/registered-artifact.ts
@@ -3,6 +3,8 @@ import type { IssueAttachment } from "../../packages/shared/src/types/issue.js";
 import type { IssueWorkProduct } from "../../packages/shared/src/types/work-product.js";
 import type { RunnerApi } from "./api.js";
 
+const REGISTERED_ARTIFACT_STATUSES = new Set(["active", "ready_for_review", "approved"]);
+
 /** Registration, attribution and downloaded bytes must agree independently. */
 export async function readRegisteredArtifacts(api: RunnerApi, issueId: string, runId: string, names: readonly string[]) {
   if (names.length === 0) return [];
@@ -13,7 +15,7 @@ export async function readRegisteredArtifacts(api: RunnerApi, issueId: string, r
   return Promise.all(names.map(async name => {
     const registered = products.filter(product => product.issueId === issueId &&
       product.createdByRunId === runId && product.type === "artifact" &&
-      product.status === "active" && product.title === name);
+      REGISTERED_ARTIFACT_STATUSES.has(product.status) && product.title === name);
     if (registered.length !== 1) throw new Error(`Expected one registered artifact ${name} from the tested run; observed ${registered.length}`);
     const product = registered[0]!;
     const attachmentId = product.metadata?.attachmentId;
diff --git a/tests/runner-e2e/runner.spec.ts b/tests/runner-e2e/runner.spec.ts
index 826ed82b4e1..07ec11372d8 100644
--- a/tests/runner-e2e/runner.spec.ts
+++ b/tests/runner-e2e/runner.spec.ts
@@ -1,3 +1,4 @@
+import { runTaskWorkspacesFlow } from "./task-workspaces-flow.js";
 import type { RestartRunnerIdentity } from "./process-tree-owner.js";
 import { runNativeActiveStopFlow } from "./native-active-stop-flow.js";
 import { runPiControlsFlow } from "./pi-controls-flow.js";
@@ -983,7 +984,17 @@ for (const execution of executions) {
         secrets,
       );
 
-      if (execution.task.flow === "public_mcp") {
+      if (execution.task.flow === "task_workspaces") {
+        const journey = await runTaskWorkspacesFlow({ page, api, fixtures, execution, nonce, temporaryRoot,
+          deadlineAt: startedAtMs + deadlineMs - 30_000,
+          restart: () => restartIsolatedPaperclipServer({ api, requestId: `task-workspaces-${nonce}`, deadlineAt: startedAtMs + deadlineMs }),
+          observe: (currentIssue, currentRuns, checks) => {
+            issue = currentIssue as IssueRecord; selectedRuns = currentRuns as RunRecord[];
+            matcherResults = checks.map(check => ({ matcher: { kind: "json_path" as const, path: `taskWorkspaces.${check.id}`, expected: true }, passed: check.passed, detail: check.detail }));
+          }, capture: captureScreenshot, evidence: (name, data) => writeSanitizedJson(snapshotsDir, name, data, secrets),
+        });
+        issue = journey.issue as IssueRecord; selectedRuns = journey.runs as RunRecord[];
+      } else if (execution.task.flow === "public_mcp") {
         const journey = await runPublicMcpFlow({
           page, api, fixtures, execution, nonce, secrets, userId: publicMcpUserId,
           credential: credentials[execution.profile.credential]!, usage: publicMcpUsage!, deadlineAt: startedAtMs + deadlineMs,
diff --git a/tests/runner-e2e/task-workspaces-cases.ts b/tests/runner-e2e/task-workspaces-cases.ts
new file mode 100644
index 00000000000..ab8a80f4b1d
--- /dev/null
+++ b/tests/runner-e2e/task-workspaces-cases.ts
@@ -0,0 +1,73 @@
+import { createHash } from "node:crypto";
+import { readFileSync } from "node:fs";
+import type { RunnerProfileFixture, RunnerTaskFixture } from "./types.js";
+
+export const TASK_WORKSPACES_SUITE = "task-workspaces";
+export const TASK_WORKSPACES_BUDGET_CENTS = 1_000;
+export const TASK_REPOSITORY_URL = "https://github.com/octocat/Hello-World";
+export function taskWorkspaceFiles(nonce: string) {
+  return { note: `task-note-${nonce}.txt`, proof: `task-proof-${nonce}.txt`, repositoryFile: `repository-proof-${nonce}.txt`,
+    noteBytes: (turn: number) => `task-files-turn-${turn}-${nonce}\n`, repositoryBytes: `repository-dirty-${nonce}\n` };
+}
+
+export function taskWorkspaceProfile(profile: RunnerProfileFixture): RunnerProfileFixture {
+  return { ...profile, buildAgent(input) {
+    const agent = profile.buildAgent(input);
+    const { cwd: _cwd, ...adapterConfig } = agent.adapterConfig as Record;
+    return { ...agent, adapterConfig, budgetMonthlyCents: TASK_WORKSPACES_BUDGET_CENTS };
+  } };
+}
+
+export function taskWorkspacePrompt(nonce: string, turn: 1 | 2 | 3, native: boolean) {
+  const files = taskWorkspaceFiles(nonce);
+  const inspect = native ? "Use get_workspace to inspect your admitted task workspace."
+    : "GET /api/issues/$PAPERCLIP_TASK_ID/workspace through the authenticated public API to inspect your task workspace.";
+  const api = native ? "Use the real Paperclip semantic tools."
+    : 'Use the injected PAPERCLIP_API_URL, PAPERCLIP_API_KEY and PAPERCLIP_RUN_ID for the public API. Normalize API_ORIGIN="${PAPERCLIP_API_URL%/}"; API_ORIGIN="${API_ORIGIN%/api}". Include Authorization: Bearer and X-Paperclip-Run-Id on API requests. Never print credentials.';
+  const content = turn === 1 ? [
+    "This is the first of three separately requested steps. Do only this step now; a later user message will request the next step.",
+    inspect,
+    `In the current task directory write ${files.note} with exactly ${JSON.stringify(files.noteBytes(1))}. Keep task files outside AGENT_HOME.`,
+    "Stage a repository for the NEXT normal admission; do not clone it yourself, create a project, switch roots, wait for a new run, or wake this task.",
+    native ? `Call prepare_repository twice with exactly {repository:{kind:"url",url:"${TASK_REPOSITORY_URL}"},requestKey:"repository-${nonce}"}. Both calls must report the same preparation receipt and next_normal_admission.`
+      : `POST /api/issues/$PAPERCLIP_TASK_ID/workspace/repositories twice with the same JSON {"repository":{"kind":"url","url":"${TASK_REPOSITORY_URL}"},"requestKey":"repository-${nonce}"}. Both replies must return the same operationId and next_normal_admission.`,
+    "Inspect the workspace again. Its identity/root must be unchanged and the repository should still be pending. The repository need not exist yet. The objective of this step is staged preparation, not a completed clone.",
+  ] : turn === 2 ? [
+    "Continue this same task after the controller restart. Do only step two now.", inspect,
+    `Read ${files.note} and require its exact bytes to be ${JSON.stringify(files.noteBytes(1))}; fail visibly if missing or different.`,
+    "The previously staged repository must now be ready in the reported contained relativePath. Do not prepare or clone a replacement, create a project, or switch the task root.",
+    `Inside that checkout create ${files.repositoryFile} containing exactly ${JSON.stringify(`repository-committed-${nonce}\n`)}. Commit ONLY that file using git -c user.name='Workspace Fixture' -c user.email='fixture@example.test' commit with message workspace-proof-${nonce}. Do not push.`,
+    `Then overwrite that committed file with exactly ${JSON.stringify(files.repositoryBytes)}, leaving this local change uncommitted.`,
+    `Update the task-root ${files.note} to exactly ${JSON.stringify(files.noteBytes(2))}.`,
+  ] : [
+    "Continue this same task with the final step. Do not recreate a missing repository or missing earlier files.", inspect,
+    `Read ${files.note}; require exactly ${JSON.stringify(files.noteBytes(2))}.`,
+    `Read the existing ${files.repositoryFile} in the prepared checkout. Copy its bytes unchanged to ${files.proof} in the task root. Do not edit or commit the repository in this step.`,
+    `Update ${files.note} to exactly ${JSON.stringify(files.noteBytes(3))}.`,
+    `Register ${files.proof} as a downloadable artifact work product with exact title ${files.proof}.`,
+  ];
+  return [...content, "The quoted \\n escapes denote actual LF bytes. Include the final LF when writing each exact string; verify byte counts before finishing.", api,
+    turn < 3 ? "Save a short Paperclip task document describing this completed step as its durable work product. Then complete this step and mark the task Done; do not wait for the next user message."
+      : "Validate the written and downloaded-proof source bytes before completing the task.",
+    native ? "Use paperclip_finish with reportedWorkDisposition done, the current completion contract revision, satisfied objective criterion, real evidence references, and no remaining work for THIS requested step."
+      : "Complete through the normal public task API after saving the work product.",
+    `Use TASK-WORKSPACE-${turn}-${nonce} as the completion summary and final response. Do not do unrelated work.`,
+  ].join("\n");
+}
+
+export const taskWorkspaceTask: RunnerTaskFixture = {
+  id: "task-directory-repository-resume", label: "Task files, repository preparation and restart", groups: [],
+  flow: "task_workspaces", workMode: "standard", expectedRunCount: 3, automaticRetryPolicy: "single_attempt",
+  attemptTimeoutMs: { local: 15 * 60_000, daytona: 15 * 60_000 }, turnTimeoutMs: 4 * 60_000,
+  expectedTerminalState: { issue: "done", run: "succeeded" },
+  buildTitle: nonce => `Task files without a project ${nonce}`,
+  buildVisibleMarker: nonce => `TASK-WORKSPACE-3-${nonce}`,
+  buildPrompt: nonce => taskWorkspacePrompt(nonce, 1, true), buildMatchers: () => [],
+};
+export function taskWorkspaceDefinitionDigest() {
+  const hash = createHash("sha256");
+  for (const file of ["task-workspaces-cases.ts", "task-workspaces-flow.ts", "task-workspaces-scoring.ts", "registered-artifact.ts", "live-fixtures.ts", "runner.spec.ts"]) {
+    hash.update(file).update(readFileSync(new URL(file, import.meta.url)));
+  }
+  return hash.digest("hex");
+}
diff --git a/tests/runner-e2e/task-workspaces-flow.ts b/tests/runner-e2e/task-workspaces-flow.ts
new file mode 100644
index 00000000000..b7add6b59f6
--- /dev/null
+++ b/tests/runner-e2e/task-workspaces-flow.ts
@@ -0,0 +1,182 @@
+import { expect, type Page } from "@playwright/test";
+import { execFile as execFileCallback } from "node:child_process";
+import { promisify } from "node:util";
+import { realpath } from "node:fs/promises";
+import path from "node:path";
+import { pollUntil, type RunnerApi } from "./api.js";
+import { createTaskThroughUi, submitTaskReply } from "./user-actions.js";
+import { collectRunEvents } from "./run-observations.js";
+import { restartChatServer } from "./chat-restart.js";
+import { readRegisteredArtifacts } from "./registered-artifact.js";
+import { TASK_WORKSPACES_BUDGET_CENTS, taskWorkspaceFiles, taskWorkspacePrompt } from "./task-workspaces-cases.js";
+import { gradeTaskWorkspaces, isRepositoryPreparationReceipt, type TaskWorkspaceCheckpoint, type TaskWorkspaceObservation } from "./task-workspaces-scoring.js";
+import type { LiveFixtureValues } from "./live-fixtures.js";
+import type { MatrixExecution } from "./types.js";
+
+type Row = Record;
+const execFile = promisify(execFileCallback);
+const terminal = new Set(["succeeded", "failed", "cancelled", "timed_out"]);
+
+export async function runTaskWorkspacesFlow(input: {
+  page: Page; api: RunnerApi; fixtures: LiveFixtureValues; execution: MatrixExecution; nonce: string;
+  temporaryRoot: string; deadlineAt: number;
+  restart(): Promise;
+  observe(issue: Row, runs: Row[], checks: ReturnType): void;
+  capture(id: string, label: string, file: string): Promise;
+  evidence(name: string, value: unknown): Promise;
+}) {
+  const { page, api, fixtures, execution, nonce } = input;
+  const native = execution.profile.generation === "native";
+  const files = taskWorkspaceFiles(nonce);
+  const companyPath = `/api/companies/${fixtures.company.id}`;
+  let issue: Row | undefined;
+  let runs: Row[] = [];
+  const evidence: TaskWorkspaceObservation = { nonce, native, daytona: execution.environment.id === "daytona", initial: null,
+    checkpoints: [], projectCount: -1, runCount: 0, restarted: false, pendingInteractions: -1, activeOperations: -1,
+    activeRecovery: true, artifact: null, remoteLeaseRunIds: [] };
+  const eventEvidence: Array<{ runId: string; events: Row[] }> = [];
+  async function refresh() {
+    if (!issue) return;
+    issue = await api.get(`/api/issues/${issue.id}`);
+    const listed = await api.get(`${companyPath}/heartbeat-runs?limit=100`);
+    runs = await Promise.all(listed.map(run => api.get(`/api/heartbeat-runs/${run.id}`)));
+    runs.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)) || a.id.localeCompare(b.id));
+    evidence.runCount = runs.length;
+  }
+  async function save(phase: string) {
+    const checks = gradeTaskWorkspaces(evidence);
+    if (issue) input.observe(issue, runs, checks);
+    await input.evidence("task-workspaces.json", { schema: "paperclip.e2e.task-workspaces.v1", phase,
+      budgetMonthlyCents: TASK_WORKSPACES_BUDGET_CENTS, expectedTurns: 3, observation: evidence, checks });
+    await input.evidence("task-workspaces-runs.json", { runs, events: eventEvidence });
+  }
+  async function readFile(relativePath: string) {
+    const response = await api.get(`/api/issues/${issue!.id}/file-resources/content?workspace=execution&path=${encodeURIComponent(relativePath)}`);
+    if (response.resource?.workspaceKind !== "execution_workspace" || typeof response.content?.data !== "string") {
+      throw new Error("Task file oracle did not return execution-workspace text bytes");
+    }
+    return response.content.data as string;
+  }
+  async function observeWorkspace() {
+    const view = await api.get(`/api/issues/${issue!.id}/workspace`);
+    if (!view.workspace?.id || !view.workspace.cwd) throw new Error("Task has no durable workspace binding");
+    const cwd = await realpath(view.workspace.cwd);
+    const isolated = await realpath(input.temporaryRoot);
+    const suffix = `/isolated-workspaces/${fixtures.company.id}/${issue!.id}`;
+    const confinedTaskRoot = cwd.startsWith(`${isolated}${path.sep}`) && cwd.endsWith(suffix);
+    if (!confinedTaskRoot) throw new Error("Task workspace is not its confined company/task directory");
+    return { view, cwd, confinedTaskRoot };
+  }
+  async function settled(turn: number) {
+    await pollUntil({ label: `task workspace turn ${turn} durably settled`,
+      deadlineAt: Math.min(input.deadlineAt, Date.now() + (execution.task.turnTimeoutMs ?? 240_000)), intervalMs: 750,
+      load: async () => {
+        await refresh();
+        const [interactions, recovery] = await Promise.all([
+          api.get(`/api/issues/${issue!.id}/interactions`), api.get(`/api/issues/${issue!.id}/recovery-actions`),
+        ]);
+        const operations = (await Promise.all(runs.map(run => api.get(`/api/heartbeat-runs/${run.id}/workspace-operations`)))).flat();
+        evidence.pendingInteractions = interactions.filter(row => row.status === "pending").length;
+        evidence.activeOperations = operations.filter(row => ["pending", "queued", "running"].includes(row.status)).length;
+        evidence.activeRecovery = [recovery.active, ...(recovery.actions ?? [])].some(row => row
+          && ["active", "pending"].includes(row.status) && row.wakePolicy?.kind === "resume_native_run");
+        return { issue, runs };
+      },
+      accept: state => state.runs.length === turn && state.issue?.status === "done" && state.runs.every(run => run.status === "succeeded"
+        && (!native || (run.nativePhase === "committed" && run.resultJson?.finalizationPhase === "committed")))
+        && evidence.pendingInteractions === 0 && evidence.activeOperations === 0 && !evidence.activeRecovery,
+      reject: state => state.runs.length > turn ? "Unexpected extra provider run" : state.runs.some(run => terminal.has(run.status) && run.status !== "succeeded") ? "A requested turn failed" : undefined,
+    });
+  }
+  async function checkpoint(turn: 1 | 2 | 3) {
+    const { view, cwd, confinedTaskRoot } = await observeWorkspace();
+    const run = runs[turn - 1]!;
+    if (run.companyId !== fixtures.company.id || run.agentId !== fixtures.agent.id
+      || ![run.nativeIssueId, run.issueId, run.contextSnapshot?.issueId, run.contextSnapshot?.taskId].includes(issue!.id)) {
+      throw new Error("Provider run is not attributed to the selected task and agent");
+    }
+    const events = await collectRunEvents((afterSeq, limit) => api.get(`/api/heartbeat-runs/${run.id}/events?afterSeq=${afterSeq}&limit=${limit}`));
+    eventEvidence.push({ runId: run.id, events });
+    const spans = events.filter(event => event.eventType === "run.performance.span").map(event => event.payload);
+    const nativeInput = run.runnerProfileJson?.nativeExecutionInput;
+    const point: TaskWorkspaceCheckpoint = { turn,
+      issue: { id: issue!.id, projectId: issue!.projectId, executionWorkspaceId: issue!.executionWorkspaceId, status: issue!.status, scheduledRetry: issue!.scheduledRetry },
+      workspace: { id: view.workspace.id, projectId: view.workspace.projectId, cwd }, confinedTaskRoot,
+      note: await readFile(files.note), repositories: view.repositories,
+      run: { id: run.id, status: run.status, runtimeMode: run.runtimeMode, nativePhase: run.nativePhase,
+        nativeBinding: nativeInput?.binding?.executionWorkspaceId, nativeCwd: nativeInput?.workspace?.cwd,
+        transport: spans.find(p => p?.span === "runner.transport.selected")?.mode,
+        authenticated: spans.some(p => p?.span === "runner.prp.authenticate" && p.outcome === "ok"),
+        nativeCompleted: events.some(event => event.eventType === "run.result.accepted" && event.payload?.prpEvent?.sourceKind === "control_plane"
+          && event.payload.prpEvent.runId === run.id && event.payload.prpEvent.payload?.result?.reportedWorkDisposition === "done")
+          && events.some(event => event.eventType === "run.terminal" && event.payload?.prpEvent?.sourceKind === "control_plane"
+            && event.payload.prpEvent.runId === run.id && event.payload.prpEvent.payload?.runTerminalState === "succeeded") },
+      prepareCalls: events.filter(event => isRepositoryPreparationReceipt(event, run.id)).length,
+    };
+    if (turn > 1) {
+      const repository = view.repositories[0];
+      if (!repository || !/^\.paperclip-repositories\/task-repo-[a-f0-9]{24}$/.test(repository.relativePath)) throw new Error("No contained managed repository receipt");
+      const checkout = await realpath(path.join(cwd, repository.relativePath));
+      if (!checkout.startsWith(`${cwd}${path.sep}`)) throw new Error("Repository oracle path escaped the isolated task root");
+      const git = async (...args: string[]) => (await execFile("git", ["-C", checkout, ...args], { timeout: 15_000, maxBuffer: 65_536 })).stdout.trim();
+      point.repository = { head: await git("rev-parse", "HEAD"),
+        baseIsAncestor: await git("merge-base", "--is-ancestor", repository.pinnedCommit, "HEAD").then(() => true, () => false),
+        dirty: (await git("status", "--porcelain", "--", files.repositoryFile)).length > 0,
+        content: await readFile(`${repository.relativePath}/${files.repositoryFile}`) };
+    }
+    evidence.checkpoints.push(point);
+    evidence.projectCount = (await api.get(`${companyPath}/projects`)).length;
+    if (execution.environment.id === "daytona") evidence.remoteLeaseRunIds = (await api.get(`/api/environments/${fixtures.environment.id}/leases`))
+      .filter(lease => lease.issueId === issue!.id && typeof lease.providerLeaseId === "string").map(lease => lease.heartbeatRunId);
+    await save(`turn-${turn}`);
+    await page.reload();
+    await expect(page.getByTestId("task-chat-thread-header").getByTestId("issue-detail-header")).toBeVisible();
+    await expect(page.getByTestId("task-chat-agent-bubble").filter({ hasText: `TASK-WORKSPACE-${turn}-${nonce}` }).last()).toBeVisible({ timeout: 30_000 });
+    await input.capture(`task-files-turn-${turn}`, `Projectless task after turn ${turn}`, `task-files-turn-${turn}.png`);
+  }
+  try {
+    if (fixtures.project) throw new Error("Task-workspaces fixture must not create a project");
+    const agent = await api.get(`/api/agents/${fixtures.agent.id}`);
+    if (agent.adapterConfig?.cwd) throw new Error("Task-workspaces fixture must not configure operator cwd");
+    await api.patch(`/api/companies/${fixtures.company.id}/budgets`, { budgetMonthlyCents: TASK_WORKSPACES_BUDGET_CENTS });
+    await api.patch(`/api/agents/${fixtures.agent.id}/budgets`, { budgetMonthlyCents: TASK_WORKSPACES_BUDGET_CENTS });
+    const created = await createTaskThroughUi({ page, issuePrefix: fixtures.company.issuePrefix!, agentName: fixtures.agent.name,
+      title: execution.task.buildTitle(nonce), prompt: taskWorkspacePrompt(nonce, 1, native), workMode: "standard", requireExplicitTitle: true });
+    issue = await api.get(`/api/issues/${created.issueId}`);
+    await page.goto(`/${encodeURIComponent(fixtures.company.issuePrefix!)}/issues/${encodeURIComponent(issue.identifier ?? issue.id)}`);
+    const before = await pollUntil({ label: "initial projectless workspace admission", deadlineAt: input.deadlineAt,
+      load: () => api.get(`/api/issues/${issue!.id}/workspace`), accept: view => Boolean(view.workspace?.id), intervalMs: 300 });
+    evidence.initial = { workspaceId: before.workspace.id, cwd: await realpath(before.workspace.cwd), repositories: before.repositories.length };
+    await save("initial-admission");
+    await settled(1); await checkpoint(1);
+    const issueUrl = page.url();
+    await restartChatServer(page, async () => { await input.restart(); });
+    evidence.restarted = true;
+    await page.goto(issueUrl);
+    await expect(page.getByTestId("task-chat-thread-header").getByTestId("issue-detail-header")).toBeVisible();
+    await submitTaskReply(page, taskWorkspacePrompt(nonce, 2, native));
+    await settled(2); await checkpoint(2);
+    await submitTaskReply(page, taskWorkspacePrompt(nonce, 3, native));
+    await settled(3); await checkpoint(3);
+    const [artifact] = await readRegisteredArtifacts(api, issue.id, runs[2]!.id, [files.proof]);
+    evidence.artifact = artifact;
+    await save("completed");
+    const checks = gradeTaskWorkspaces(evidence);
+    const failed = checks.filter(check => !check.passed);
+    if (failed.length) throw new Error(`Task workspace oracle failure: ${failed.map(check => check.id).join(", ")}`);
+    await refresh();
+    const [comments, interactions] = await Promise.all([
+      api.get(`/api/issues/${issue.id}/comments`),
+      api.get(`/api/issues/${issue.id}/interactions`),
+    ]);
+    await input.evidence("api-state.json", { issue, runs, comments, interactions, checks });
+    await expect(page.getByTestId("task-chat-thread-header").getByTestId("issue-detail-header")
+      .getByRole("button", { name: "Change status (current: Done)", exact: true })).toBeVisible();
+    await input.capture("final-state", "Final visible task state", "final-state.png");
+    return { issue, runs, checks };
+  } catch (error) {
+    await refresh().catch(() => undefined);
+    await save("failed");
+    throw error;
+  }
+}
diff --git a/tests/runner-e2e/task-workspaces-scoring.ts b/tests/runner-e2e/task-workspaces-scoring.ts
new file mode 100644
index 00000000000..205ec6f80d8
--- /dev/null
+++ b/tests/runner-e2e/task-workspaces-scoring.ts
@@ -0,0 +1,83 @@
+export interface TaskWorkspaceCheckpoint {
+  turn: number;
+  issue: { id: string; projectId: string | null; executionWorkspaceId: string | null; status: string; scheduledRetry?: unknown };
+  workspace: { id: string; projectId: string | null; cwd: string };
+  confinedTaskRoot: boolean;
+  note: string;
+  repositories: Array<{ id: string; state: string; relativePath: string; pinnedCommit: string | null; requestedByIssueId: string | null; requestKeys: string[] }>;
+  run: { id: string; status: string; runtimeMode?: string; nativePhase?: string; nativeBinding?: string; nativeCwd?: string; transport?: string; authenticated?: boolean; nativeCompleted?: boolean };
+  repository?: { head: string; baseIsAncestor: boolean; dirty: boolean; content: string };
+  prepareCalls?: number;
+}
+export interface TaskWorkspaceObservation {
+  nonce: string;
+  native: boolean;
+  daytona: boolean;
+  initial: { workspaceId: string; cwd: string; repositories: number } | null;
+  checkpoints: TaskWorkspaceCheckpoint[];
+  projectCount: number;
+  runCount: number;
+  restarted: boolean;
+  pendingInteractions: number;
+  activeOperations: number;
+  activeRecovery: boolean;
+  artifact: { content: string; contentVerified: boolean; mimeType: string } | null;
+  remoteLeaseRunIds: string[];
+}
+
+export function gradeTaskWorkspaces(value: TaskWorkspaceObservation) {
+  const checks: Array<{ id: string; passed: boolean; detail: string }> = [];
+  const check = (id: string, passed: boolean, detail: string) => checks.push({ id, passed, detail });
+  const [first, second, third] = value.checkpoints;
+  const complete = value.checkpoints.length === 3 && value.checkpoints.every((p, i) => p.turn === i + 1);
+  check("three-attributed-turns", complete && value.runCount === 3 && new Set(value.checkpoints.map(p => p.run.id)).size === 3
+    && value.checkpoints.every(p => p.run.status === "succeeded" && p.issue.status === "done" && p.issue.scheduledRetry == null),
+  "Exactly three successful requested turns with no hidden retries or unfinished tasks.");
+  check("projectless-task-owned-root", complete && value.projectCount === 0 && value.checkpoints.every(p =>
+    p.issue.projectId === null && p.workspace.projectId === null && p.confinedTaskRoot && p.issue.executionWorkspaceId === p.workspace.id),
+  "Task and execution workspace remain projectless, with a confined company/task directory rather than agent home.");
+  check("immutable-root-and-binding", complete && value.initial !== null &&
+    value.checkpoints.every(p => p.workspace.id === value.initial!.workspaceId && p.workspace.cwd === value.initial!.cwd),
+  "The independently observed initial root and binding survive all three admissions.");
+  check("task-file-continuity", complete && value.checkpoints.every(p => p.note === `task-files-turn-${p.turn}-${value.nonce}\n`),
+  "Public execution-workspace file reads match the exact expected bytes after every turn.");
+  const a = first?.repositories[0], b = second?.repositories[0], c = third?.repositories[0];
+  check("deferred-idempotent-acquisition", complete && value.checkpoints.every(p => p.repositories.length === 1 &&
+    p.repositories[0]?.requestedByIssueId === p.issue.id && p.repositories[0]?.requestKeys.length === 1 && p.repositories[0]?.requestKeys[0] === `repository-${value.nonce}`)
+    && a?.state === "pending" && a.pinnedCommit === null && a.id === b?.id && a.id === c?.id
+    && /^\.paperclip-repositories\/task-repo-[a-f0-9]{24}$/.test(a.relativePath)
+    && a.relativePath === b?.relativePath && a.relativePath === c?.relativePath && b?.state === "ready" && c?.state === "ready"
+    && /^[a-f0-9]{40,64}$/.test(b?.pinnedCommit ?? "") && b?.pinnedCommit === c?.pinnedCommit,
+  "Duplicate preparation converges to one pending receipt, realized only on the next admission with a stable contained path and pinned source.");
+  check("nested-git-and-dirty-files", complete && second?.repository !== undefined && third?.repository !== undefined
+    && second.repository.baseIsAncestor && third.repository.baseIsAncestor && second.repository.head !== b?.pinnedCommit
+    && second.repository.head === third.repository.head && second.repository.dirty && third.repository.dirty
+    && second.repository.content === `repository-dirty-${value.nonce}\n` && third.repository.content === second.repository.content,
+  "A new local commit and subsequent uncommitted bytes in a nested repository survive the next admission.");
+  check("controller-restart", value.restarted && complete, "The isolated controller restarted between the first and second requested turns.");
+  check("downloaded-artifact", value.artifact?.contentVerified === true && value.artifact.mimeType === "text/plain"
+    && value.artifact.content === `repository-dirty-${value.nonce}\n`,
+  "The final run's registered downloadable artifact has verified metadata, digest and exact prior repository bytes.");
+  check("settled-control-plane", value.pendingInteractions === 0 && value.activeOperations === 0 && !value.activeRecovery,
+    "No unanswered decision, workspace operation or scheduled native recovery remains.");
+  check("runtime-and-transport", complete && value.checkpoints.every(p => value.native
+    ? p.run.runtimeMode === "native" && p.run.nativePhase === "committed" && p.run.nativeCompleted === true && p.run.nativeBinding === p.workspace.id
+      && typeof p.run.nativeCwd === "string" && p.run.nativeCwd.length > 0
+      && p.run.transport === (value.daytona ? "provider_ingress" : "local_loopback") && (!value.daytona || p.run.authenticated === true)
+    : p.run.runtimeMode === "legacy"), "Each observed run uses the selected actual runtime and native transport/admitted binding.");
+  check("semantic-repository-tool", !value.native || first?.prepareCalls === 2,
+    "Native preparation uses two successful real prepare_repository tool calls, not a model-authored clone command.");
+  check("daytona-run-leases", !value.daytona || (complete && value.checkpoints.every(p => value.remoteLeaseRunIds.includes(p.run.id))),
+    "Every remote turn has a public Daytona environment lease attributed to that run.");
+  return checks;
+}
+
+export function isRepositoryPreparationReceipt(event: {
+  eventType?: string;
+  payload?: { prpEvent?: { sourceKind?: string; runId?: string; payload?: { name?: string; status?: string } } };
+}, runId: string) {
+  const receipt = event.payload?.prpEvent;
+  return event.eventType === "tool.execution.completed" && receipt?.sourceKind === "runner"
+    && receipt.runId === runId && receipt.payload?.status === "completed"
+    && ["prepare_repository", "paperclip.prepare_repository", "paperclip_prepare_repository"].includes(receipt.payload.name ?? "");
+}
diff --git a/tests/runner-e2e/task-workspaces.test.ts b/tests/runner-e2e/task-workspaces.test.ts
new file mode 100644
index 00000000000..fb334f12a6a
--- /dev/null
+++ b/tests/runner-e2e/task-workspaces.test.ts
@@ -0,0 +1,92 @@
+import { describe, expect, it } from "vitest";
+import { runnerMatrix } from "./catalog.js";
+import { gradeTaskWorkspaces, isRepositoryPreparationReceipt, type TaskWorkspaceObservation } from "./task-workspaces-scoring.js";
+import { TASK_WORKSPACES_SUITE, taskWorkspaceFiles, taskWorkspacePrompt } from "./task-workspaces-cases.js";
+
+function valid(): TaskWorkspaceObservation {
+  const nonce = "fixture", cwd = "/isolated/task", pin = "a".repeat(40), head = "b".repeat(40);
+  return { nonce, native: true, daytona: true, initial: { workspaceId: "workspace", cwd, repositories: 0 },
+    checkpoints: [1, 2, 3].map(turn => ({ turn,
+      issue: { id: "issue", projectId: null, executionWorkspaceId: "workspace", status: "done" },
+      workspace: { id: "workspace", projectId: null, cwd }, confinedTaskRoot: true,
+      note: `task-files-turn-${turn}-${nonce}\n`,
+      repositories: [{ id: "repo", state: turn === 1 ? "pending" : "ready", relativePath: `.paperclip-repositories/task-repo-${"a".repeat(24)}`,
+        pinnedCommit: turn === 1 ? null : pin, requestedByIssueId: "issue", requestKeys: [`repository-${nonce}`] }],
+      run: { id: `run-${turn}`, status: "succeeded", runtimeMode: "native", nativePhase: "committed", nativeBinding: "workspace",
+        nativeCwd: "/workspace/task", transport: "provider_ingress", authenticated: true, nativeCompleted: true },
+      ...(turn > 1 ? { repository: { head, baseIsAncestor: true, dirty: true, content: `repository-dirty-${nonce}\n` } } : {}),
+      prepareCalls: turn === 1 ? 2 : 0,
+    })), projectCount: 0, runCount: 3, restarted: true, pendingInteractions: 0, activeOperations: 0, activeRecovery: false,
+    artifact: { content: `repository-dirty-${nonce}\n`, contentVerified: true, mimeType: "text/plain" },
+    remoteLeaseRunIds: ["run-1", "run-2", "run-3"] };
+}
+describe("task workspace product E2E", () => {
+  it("declares exactly the three authorized cells with no project/cwd and one attempt", () => {
+    const cells = runnerMatrix.filter(cell => cell.suite.id === TASK_WORKSPACES_SUITE);
+    expect(cells.map(cell => cell.id).sort()).toEqual([
+      "task-workspaces.legacy-opencode.local.task-directory-repository-resume",
+      "task-workspaces.runner-opencode.daytona.task-directory-repository-resume",
+      "task-workspaces.runner-opencode.local.task-directory-repository-resume",
+    ]);
+    for (const cell of cells) {
+      expect(cell.suite.manualOnly).toBe(true);
+      expect(cell.task.automaticRetryPolicy).toBe("single_attempt");
+      expect(cell.task.expectedRunCount).toBe(3);
+      const agent = cell.profile.buildAgent({ environmentId: "environment", environmentFixtureId: cell.environment.id,
+        workspacePath: "/must-not-use", executionId: "fixture", secretRefs: { OPENROUTER_API_KEY: { type: "secret_ref", secretId: "secret", version: "latest" } } });
+      expect(agent.budgetMonthlyCents).toBe(1000);
+      expect((agent.adapterConfig as Record).cwd).toBeUndefined();
+    }
+  });
+  it("keeps native tools and legacy authenticated API prompts distinct", () => {
+    expect(taskWorkspacePrompt("fixture", 1, true)).toContain("Call prepare_repository twice");
+    expect(taskWorkspacePrompt("fixture", 1, false)).toContain("POST /api/issues/$PAPERCLIP_TASK_ID/workspace/repositories twice");
+    expect(taskWorkspacePrompt("fixture", 3, true)).not.toContain(taskWorkspaceFiles("fixture").repositoryBytes.trim());
+  });
+  it("recognizes the normalized provider receipt and rejects claims from another source/run", () => {
+    const receipt = { eventType: "tool.execution.completed", payload: { prpEvent: {
+      sourceKind: "runner", runId: "run-1", payload: { name: "paperclip_prepare_repository", status: "completed" },
+    } } };
+    expect(isRepositoryPreparationReceipt(receipt, "run-1")).toBe(true);
+    expect(isRepositoryPreparationReceipt(receipt, "another-run")).toBe(false);
+    receipt.payload.prpEvent.sourceKind = "control_plane";
+    expect(isRepositoryPreparationReceipt(receipt, "run-1")).toBe(false);
+    receipt.payload.prpEvent.sourceKind = "runner";
+    receipt.payload.prpEvent.payload.status = "failed";
+    expect(isRepositoryPreparationReceipt(receipt, "run-1")).toBe(false);
+  });
+  it("accepts independently consistent persisted observations", () => {
+    expect(gradeTaskWorkspaces(valid()).filter(check => !check.passed)).toEqual([]);
+    const legacy = valid(); legacy.native = false; legacy.daytona = false;
+    legacy.checkpoints.forEach(point => { point.run.runtimeMode = "legacy"; delete point.prepareCalls; });
+    expect(gradeTaskWorkspaces(legacy).filter(check => !check.passed)).toEqual([]);
+  });
+  it("accepts a pending receipt queued before the first browser observation", () => {
+    const observation = valid();
+    observation.initial!.repositories = 1;
+    expect(gradeTaskWorkspaces(observation).filter(check => !check.passed)).toEqual([]);
+    observation.checkpoints[0]!.repositories[0]!.state = "ready";
+    expect(gradeTaskWorkspaces(observation).find(check => check.id === "deferred-idempotent-acquisition")?.passed).toBe(false);
+  });
+  it.each([
+    ["missing evidence", (v: TaskWorkspaceObservation) => { v.checkpoints = []; }],
+    ["agent home", (v: TaskWorkspaceObservation) => { v.checkpoints[0]!.confinedTaskRoot = false; }],
+    ["new project", (v: TaskWorkspaceObservation) => { v.projectCount = 1; }],
+    ["root switched", (v: TaskWorkspaceObservation) => { v.checkpoints[1]!.workspace.cwd = "/different"; }],
+    ["early clone", (v: TaskWorkspaceObservation) => { v.checkpoints[0]!.repositories[0]!.state = "ready"; }],
+    ["duplicate receipt", (v: TaskWorkspaceObservation) => { v.checkpoints[1]!.repositories.push(v.checkpoints[1]!.repositories[0]!); }],
+    ["different pin", (v: TaskWorkspaceObservation) => { v.checkpoints[2]!.repositories[0]!.pinnedCommit = "c".repeat(40); }],
+    ["lost Git commit", (v: TaskWorkspaceObservation) => { v.checkpoints[2]!.repository!.head = "a".repeat(40); }],
+    ["lost dirty bytes", (v: TaskWorkspaceObservation) => { v.checkpoints[2]!.repository!.content = "reset"; }],
+    ["missing download", (v: TaskWorkspaceObservation) => { v.artifact = null; }],
+    ["unverified download", (v: TaskWorkspaceObservation) => { v.artifact!.contentVerified = false; }],
+    ["extra wake", (v: TaskWorkspaceObservation) => { v.runCount = 4; }],
+    ["unfinished persistence", (v: TaskWorkspaceObservation) => { v.activeOperations = 1; }],
+    ["wrong native binding", (v: TaskWorkspaceObservation) => { v.checkpoints[2]!.run.nativeBinding = "another"; }],
+    ["tool bypass", (v: TaskWorkspaceObservation) => { v.checkpoints[0]!.prepareCalls = 0; }],
+    ["missing remote lease", (v: TaskWorkspaceObservation) => { v.remoteLeaseRunIds = ["run-1"]; }],
+  ] as const)("rejects %s instead of accepting model claims", (_label, mutate) => {
+    const observation = valid(); mutate(observation);
+    expect(gradeTaskWorkspaces(observation).some(check => !check.passed)).toBe(true);
+  });
+});
diff --git a/tests/runner-e2e/types.ts b/tests/runner-e2e/types.ts
index 3c472a84ef7..dd9a1011bcc 100644
--- a/tests/runner-e2e/types.ts
+++ b/tests/runner-e2e/types.ts
@@ -17,6 +17,7 @@ export type RunnerGeneration = "legacy" | "native";
 export type RunnerEnvironmentId = "local" | "daytona";
 export type RunnerTaskWorkMode = "standard" | "planning" | "ask";
 export type RunnerTaskFlow =
+  | "task_workspaces"
   | "repository_skills"
   | "provider_connection"
   | "plan_task_guidance"
diff --git a/ui/src/api/chatEndpoints.ts b/ui/src/api/chatEndpoints.ts
index 73aa8080ef4..79e0c43b15f 100644
--- a/ui/src/api/chatEndpoints.ts
+++ b/ui/src/api/chatEndpoints.ts
@@ -36,6 +36,7 @@ export type ChatEndpointSetupAction =
   "configure" | "verify" | "pause" | "resume" | "reconnect" | "remove";
 
 export interface ChatEndpointResource {
+  executionDefaults?: import("@paperclipai/shared").ChatExecutionDefaults | null;
   metadata?: Record;
   id: string;
   type: string;
@@ -101,6 +102,7 @@ export interface ChatIdentityLinkPreview {
 }
 
 export interface ChatEndpoint {
+  executionDefaults?: import("@paperclipai/shared").ChatExecutionDefaults | null;
   communicationInstructions?: string;
   publicationMode?: "automatic" | "explicit";
   externalExecutionPolicy?: "restricted" | "agent";
@@ -249,7 +251,7 @@ export const chatEndpointsApi = {
     ),
   updateResources: (
     endpointId: string,
-    resources: Array<{ id: string; enabled: boolean }>,
+    resources: Array<{ id: string; enabled?: boolean; executionDefaults?: import("@paperclipai/shared").ChatExecutionDefaults | null }>,
   ) =>
     api.put(`/chat-endpoints/${endpointId}/resources`, {
       resources,
diff --git a/ui/src/api/execution-workspaces.test.ts b/ui/src/api/execution-workspaces.test.ts
index a247d0ac45d..6a018701aa5 100644
--- a/ui/src/api/execution-workspaces.test.ts
+++ b/ui/src/api/execution-workspaces.test.ts
@@ -28,6 +28,13 @@ describe("executionWorkspacesApi.listSummaries", () => {
     );
   });
 
+  it("delegates task selection eligibility to the server", async () => {
+    await executionWorkspacesApi.listSummaries("company-1", { selectableForTask: true });
+    expect(mockApi.get).toHaveBeenCalledWith(
+      "/companies/company-1/execution-workspaces?selectableForTask=true&summary=true",
+    );
+  });
+
   it("requests and normalizes the bounded overview payload", async () => {
     mockApi.get.mockResolvedValue({
       items: [
diff --git a/ui/src/api/execution-workspaces.ts b/ui/src/api/execution-workspaces.ts
index 05ee341c629..7f90504fd2c 100644
--- a/ui/src/api/execution-workspaces.ts
+++ b/ui/src/api/execution-workspaces.ts
@@ -59,6 +59,7 @@ export const executionWorkspacesApi = {
       issueId?: string;
       status?: string;
       reuseEligible?: boolean;
+      selectableForTask?: boolean;
     },
   ) => {
     const params = new URLSearchParams();
@@ -67,6 +68,7 @@ export const executionWorkspacesApi = {
     if (filters?.issueId) params.set("issueId", filters.issueId);
     if (filters?.status) params.set("status", filters.status);
     if (filters?.reuseEligible) params.set("reuseEligible", "true");
+    if (filters?.selectableForTask) params.set("selectableForTask", "true");
     params.set("summary", "true");
     const qs = params.toString();
     return api.get(
diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx
index bd02c27385b..189a135323b 100644
--- a/ui/src/components/IssueProperties.test.tsx
+++ b/ui/src/components/IssueProperties.test.tsx
@@ -16,6 +16,7 @@ import type { Issue, IssueDocument } from "@paperclipai/shared";
 import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
 import { getLastProjectId } from "../lib/recent-projects";
 import { IssueProperties } from "./IssueProperties";
+import { applyOptimisticIssueFieldUpdate } from "../lib/optimistic-issue-comments";
 import { queryKeys } from "../lib/queryKeys";
 
 const mockAgentsApi = vi.hoisted(() => ({
@@ -1872,6 +1873,36 @@ describe("IssueProperties", () => {
     localStorage.clear();
   });
 
+  it("changes only project organization while preserving the bound workspace", async () => {
+    mockProjectsApi.list.mockResolvedValue([createProject({ name: "Target Project" })]);
+    const workspace = createExecutionWorkspace({ projectId: null, projectWorkspaceId: null });
+    const issue = createIssue({
+      executionWorkspaceId: workspace.id,
+      currentExecutionWorkspace: workspace,
+    });
+    const onUpdate = vi.fn();
+    const root = renderProperties(container, { issue, childIssues: [], onUpdate, inline: true });
+    await flush();
+    await act(() => findRowTrigger(container, "Project")!.click());
+    const project = [...document.querySelectorAll("button")]
+      .find((button) => button.textContent?.trim() === "Target Project")!;
+    act(() => project.click());
+    expect(onUpdate).toHaveBeenLastCalledWith({ projectId: "project-1" });
+    const assigned = applyOptimisticIssueFieldUpdate(issue, onUpdate.mock.lastCall![0])!;
+    expect(assigned.executionWorkspaceId).toBe(workspace.id);
+    expect(assigned.currentExecutionWorkspace).toBe(workspace);
+    await act(() => findRowTrigger(container, "Project")!.click());
+    const none = [...document.querySelectorAll("button")]
+      .find((button) => button.textContent?.trim() === "No project")!;
+    act(() => none.click());
+    expect(onUpdate).toHaveBeenLastCalledWith({ projectId: null });
+    const cleared = applyOptimisticIssueFieldUpdate(assigned, onUpdate.mock.lastCall![0])!;
+    expect(cleared.executionWorkspaceId).toBe(workspace.id);
+    expect(cleared.currentExecutionWorkspace).toBe(workspace);
+    expect(cleared.currentExecutionWorkspace?.cwd).toBe(workspace.cwd);
+    act(() => root.unmount());
+  });
+
   it("shows a green service link above the workspace row for a live non-main workspace", async () => {
     mockProjectsApi.list.mockResolvedValue([createProject()]);
     const serviceUrl = "http://127.0.0.1:62475";
diff --git a/ui/src/components/ProjectWorkspaceSummaryCard.tsx b/ui/src/components/ProjectWorkspaceSummaryCard.tsx
index c126b390be5..09570bd29df 100644
--- a/ui/src/components/ProjectWorkspaceSummaryCard.tsx
+++ b/ui/src/components/ProjectWorkspaceSummaryCard.tsx
@@ -21,7 +21,7 @@ function truncatePath(path: string) {
 }
 
 interface ProjectWorkspaceSummaryCardProps {
-  projectRef: string;
+  projectRef: string | null;
   summary: ProjectWorkspaceSummary;
   runtimeActionKey: string | null;
   runtimeActionPending: boolean;
@@ -50,7 +50,7 @@ export function ProjectWorkspaceSummaryCard({
   const visibleIssues = summary.issues.slice(0, 4);
   const hiddenIssueCount = Math.max(summary.linkedIssueCount - visibleIssues.length, 0);
   const workspaceHref =
-    summary.kind === "project_workspace"
+    summary.kind === "project_workspace" && projectRef
       ? projectWorkspaceUrl({ id: projectRef, urlKey: projectRef }, summary.workspaceId)
       : `/execution-workspaces/${summary.workspaceId}`;
   const hasRunningServices = summary.runningServiceCount > 0;
diff --git a/ui/src/components/ProjectWorkspacesContent.tsx b/ui/src/components/ProjectWorkspacesContent.tsx
index 7ffd3cba00a..2414ca6dca8 100644
--- a/ui/src/components/ProjectWorkspacesContent.tsx
+++ b/ui/src/components/ProjectWorkspacesContent.tsx
@@ -15,8 +15,8 @@ export function ProjectWorkspacesContent({
   summaries,
 }: {
   companyId: string;
-  projectId: string;
-  projectRef: string;
+  projectId: string | null;
+  projectRef: string | null;
   summaries: ProjectWorkspaceSummary[];
 }) {
   const queryClient = useQueryClient();
@@ -35,6 +35,7 @@ export function ProjectWorkspacesContent({
     }) => {
       setRuntimeActionKey(`${input.key}:${input.action}`);
       if (input.kind === "project_workspace") {
+        if (!projectId) throw new Error("Project workspace has no project");
         return await projectsApi.controlWorkspaceRuntimeServices(projectId, input.workspaceId, input.action, companyId);
       }
       return await executionWorkspacesApi.controlRuntimeServices(input.workspaceId, input.action);
@@ -43,11 +44,11 @@ export function ProjectWorkspacesContent({
       setRuntimeActionKey(null);
       queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.overview(companyId) });
       queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId, { projectId }) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectId) });
+      if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId, { projectId }) });
+      if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectId) });
       queryClient.invalidateQueries({ queryKey: queryKeys.projects.all(companyId) });
       queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(companyId) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
+      if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
     },
   });
 
@@ -107,11 +108,11 @@ export function ProjectWorkspacesContent({
           onClosed={() => {
             queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.overview(companyId) });
             queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId) });
-            queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId, { projectId }) });
-            queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectId) });
+            if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.list(companyId, { projectId }) });
+            if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectId) });
             queryClient.invalidateQueries({ queryKey: queryKeys.projects.all(companyId) });
             queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(companyId) });
-            queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
+            if (projectId) queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
             setClosingWorkspace(null);
           }}
         />
diff --git a/ui/src/components/issue-properties/IssueProperties.tsx b/ui/src/components/issue-properties/IssueProperties.tsx
index 2eb00aa6503..29f1b57f2a3 100644
--- a/ui/src/components/issue-properties/IssueProperties.tsx
+++ b/ui/src/components/issue-properties/IssueProperties.tsx
@@ -102,8 +102,6 @@ import { ExternalObjectRows } from "./external-object-rows";
 import {
   asRecord,
   compactRecord,
-  defaultExecutionWorkspaceModeForProject,
-  defaultProjectWorkspaceIdForProject,
   isMainIssueWorkspace,
   overrideLane,
   sortAdapterModels,
@@ -633,7 +631,7 @@ export function IssueProperties({
     onSuccess: (result, request) => {
       queryClient.setQueryData(queryKeys.executionWorkspaces.detail(result.workspace.id), result.workspace);
       void queryClient.invalidateQueries({ queryKey: queryKeys.issues.detail(issue.id) });
-      void queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(result.workspace.projectId) });
+      if (result.workspace.projectId) void queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(result.workspace.projectId) });
       void queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.overview(result.workspace.companyId) });
       void queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.workspaceOperations(result.workspace.id) });
       if (companyId) {
@@ -2007,26 +2005,7 @@ export function IssueProperties({
                 option.id === (issue.projectId ?? "") && "bg-accent",
               )}
               onClick={() => {
-                if (option.kind === "project") {
-                  const defaultMode = defaultExecutionWorkspaceModeForProject(option.project);
-                  onUpdate({
-                    projectId: option.project.id,
-                    projectWorkspaceId: defaultProjectWorkspaceIdForProject(option.project),
-                    executionWorkspaceId: null,
-                    executionWorkspacePreference: workspaceIsolationControlsVisible ? defaultMode : null,
-                    executionWorkspaceSettings: workspaceIsolationControlsVisible && option.project.executionWorkspacePolicy?.enabled
-                      ? { mode: defaultMode }
-                      : null,
-                  });
-                } else {
-                  onUpdate({
-                    projectId: null,
-                    projectWorkspaceId: null,
-                    executionWorkspaceId: null,
-                    executionWorkspacePreference: null,
-                    executionWorkspaceSettings: null,
-                  });
-                }
+                onUpdate({ projectId: option.kind === "project" ? option.project.id : null });
                 setProjectOpen(false);
               }}
             >
diff --git a/ui/src/lib/honeycomb-run-link.test.ts b/ui/src/lib/honeycomb-run-link.test.ts
index c563691a60d..61ca8a0c221 100644
--- a/ui/src/lib/honeycomb-run-link.test.ts
+++ b/ui/src/lib/honeycomb-run-link.test.ts
@@ -13,12 +13,13 @@ describe("Honeycomb run links", () => {
     ).resolves.toBe("ba7816bf8f01");
   });
 
-  it("builds an exact task.run query with clickable trace-id breakdowns", () => {
+  it("finds native and legacy root spans for the exact run with clickable trace-id breakdowns", () => {
     const url = new URL(buildHoneycombRunQueryUrl("ba7816bf8f01"));
     const query = JSON.parse(url.searchParams.get("query") ?? "null") as {
       calculations: Array<{ op: string }>;
       breakdowns: string[];
-      filters: Array<{ column: string; op: string; value: string }>;
+      filters: Array<{ column: string; op: string; value: string | string[] }>;
+      filter_combination: string;
     };
 
     expect(url.origin).toBe("https://ui.honeycomb.io");
@@ -27,9 +28,10 @@ describe("Honeycomb run links", () => {
     );
     expect(query.calculations).toEqual([{ op: "COUNT" }]);
     expect(query.breakdowns).toEqual(["trace.trace_id"]);
+    expect(query.filter_combination).toBe("AND");
     expect(query.filters).toEqual([
       { column: "service.name", op: "=", value: "paperclip" },
-      { column: "name", op: "=", value: "task.run" },
+      { column: "name", op: "in", value: ["task.run", "task.provider_session"] },
       {
         column: HONEYCOMB_RUN_HASH_ATTRIBUTE,
         op: "=",
diff --git a/ui/src/lib/honeycomb-run-link.ts b/ui/src/lib/honeycomb-run-link.ts
index 9efb8218edb..5478a8a0032 100644
--- a/ui/src/lib/honeycomb-run-link.ts
+++ b/ui/src/lib/honeycomb-run-link.ts
@@ -26,7 +26,7 @@ export function buildHoneycombRunQueryUrl(runIdHash: string): string {
     breakdowns: ["trace.trace_id"],
     filters: [
       { column: "service.name", op: "=", value: "paperclip" },
-      { column: "name", op: "=", value: "task.run" },
+      { column: "name", op: "in", value: ["task.run", "task.provider_session"] },
       { column: HONEYCOMB_RUN_HASH_ATTRIBUTE, op: "=", value: runIdHash },
     ],
     filter_combination: "AND",
diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx
index 69d4864800c..e1d65ba2b24 100644
--- a/ui/src/pages/ExecutionWorkspaceDetail.tsx
+++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx
@@ -803,8 +803,8 @@ export function ExecutionWorkspaceDetail() {
   const workspace = workspaceQuery.data ?? null;
 
   const projectQuery = useQuery({
-    queryKey: workspace ? [...queryKeys.projects.detail(workspace.projectId), workspace.companyId] : ["projects", "detail", "__pending__"],
-    queryFn: () => projectsApi.get(workspace!.projectId, workspace!.companyId),
+    queryKey: workspace?.projectId ? [...queryKeys.projects.detail(workspace.projectId), workspace.companyId] : ["projects", "detail", "__pending__"],
+    queryFn: () => projectsApi.get(workspace!.projectId!, workspace!.companyId),
     enabled: Boolean(workspace?.projectId),
   });
   const project = projectQuery.data ?? null;
@@ -958,7 +958,7 @@ export function ExecutionWorkspaceDetail() {
       queryClient.setQueryData(queryKeys.executionWorkspaces.detail(result.workspace.id), result.workspace);
       queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.overview(result.workspace.companyId) });
       queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.workspaceOperations(result.workspace.id) });
-      queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(result.workspace.projectId) });
+      if (result.workspace.projectId) queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(result.workspace.projectId) });
       setRuntimeActionErrorMessage(null);
       setRuntimeActionMessage(
         request.action === "run"
@@ -1426,7 +1426,7 @@ export function ExecutionWorkspaceDetail() {
               
               
               
-                {project ? {project.name} : }
+                {project ? {project.name} : }
               
               
                 {project && linkedProjectWorkspace ? (
diff --git a/ui/src/pages/Workspaces.test.tsx b/ui/src/pages/Workspaces.test.tsx
index 9ee2b3fad05..2522896c3ba 100644
--- a/ui/src/pages/Workspaces.test.tsx
+++ b/ui/src/pages/Workspaces.test.tsx
@@ -136,6 +136,18 @@ describe("Workspaces", () => {
     vi.clearAllMocks();
   });
 
+  it("links projectless task folders to workspace details without a project route", async () => {
+    mockExecutionWorkspacesApi.listOverview.mockResolvedValue(overviewResponse({ items: [{ ...overviewItem(), projectId: null, projectName: null, projectUrlKey: null }] }));
+    const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+    root = createRoot(container);
+    await act(() => root!.render());
+    await flushQueries();
+    await vi.waitFor(() => expect(container.textContent).toContain("Task workspaces"));
+    expect(container.querySelector('a[href="/execution-workspaces/workspace-1"]')).not.toBeNull();
+    expect(container.querySelector('a[href^="/projects/"]')).toBeNull();
+    client.clear();
+  });
+
   it("uses the bounded overview endpoint and renders grouped workspace cards with linked task summaries", async () => {
     mockExecutionWorkspacesApi.listOverview
       .mockResolvedValueOnce(overviewResponse({
diff --git a/ui/src/pages/Workspaces.tsx b/ui/src/pages/Workspaces.tsx
index 269dcc025c9..a2feba67c49 100644
--- a/ui/src/pages/Workspaces.tsx
+++ b/ui/src/pages/Workspaces.tsx
@@ -15,9 +15,9 @@ import { queryKeys } from "../lib/queryKeys";
 import { projectRouteRef } from "../lib/utils";
 
 type ProjectWorkspaceGroup = {
-  projectId: string;
+  projectId: string | null;
   projectName: string;
-  projectRef: string;
+  projectRef: string | null;
   summaries: ProjectWorkspaceSummary[];
   lastUpdatedAt: Date;
   runningServiceCount: number;
@@ -46,7 +46,7 @@ function overviewItemToSummary(item: WorkspaceOverviewItem): ProjectWorkspaceSum
 }
 
 function buildProjectWorkspaceGroups(items: WorkspaceOverviewItem[]): ProjectWorkspaceGroup[] {
-  const groups = new Map();
+  const groups = new Map();
   for (const item of items) {
     const existing = groups.get(item.projectId);
     const summary = overviewItemToSummary(item);
@@ -60,8 +60,8 @@ function buildProjectWorkspaceGroups(items: WorkspaceOverviewItem[]): ProjectWor
     }
     groups.set(item.projectId, {
       projectId: item.projectId,
-      projectName: item.projectName,
-      projectRef: projectRouteRef({ id: item.projectId, name: item.projectName, urlKey: item.projectUrlKey }),
+      projectName: item.projectName ?? "Task workspaces",
+      projectRef: item.projectId ? projectRouteRef({ id: item.projectId, name: item.projectName ?? "Project", urlKey: item.projectUrlKey }) : null,
       summaries: [summary],
       lastUpdatedAt: summary.lastUpdatedAt,
       runningServiceCount: summary.runningServiceCount,
@@ -133,15 +133,15 @@ export function Workspaces() {
       ) : (
         
{groups.map((group) => ( -
+
- {group.projectName} - + :

{group.projectName}

}
{group.summaries.length} workspace{group.summaries.length === 1 ? "" : "s"} diff --git a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx index dd075cf795e..3544343a539 100644 --- a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx +++ b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx @@ -7,6 +7,7 @@ import { SlackSetupAdvanced } from "./SlackAppDetails"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { SlackToolsSettings, SlackSearchAccess } from "./SlackToolSettings"; import { ChatCommunicationInstructions } from "./ChatCommunicationInstructions"; +import { ChatExecutionDefaults } from "./ChatExecutionDefaults"; import { agentsApi } from "@/api/agents"; import { ChatConversationList } from "./ChatConversationList"; import { GitHubBotManagement, GitHubReviews } from "./GitHubBotManagement"; @@ -309,7 +310,7 @@ export function ChatEndpointDetail() {
); if (endpoint.provider === "agentmail" && activeTab === "settings") - return ; + return
; const setupIncomplete = endpoint.setup?.step !== "complete" && !(endpoint.provider === "slack" && endpoint.setup?.step === "test") && @@ -369,6 +370,7 @@ export function ChatEndpointDetail() {
} {activeTab === "settings" && endpoint.provider !== "github" && } + {activeTab === "settings" && } {activeTab === "reviews" && endpoint.provider === "github" && } {activeTab === "access" && endpoint.provider === "agentmail" && } {activeTab === "access" && endpoint.provider !== "github" && endpoint.provider !== "agentmail" && ( @@ -401,6 +403,19 @@ export function ChatEndpointDetail() { ); } +function EndpointExecutionDefaults({ endpoint }: { endpoint: ChatEndpoint }) { + const queryClient = useQueryClient(); + return
{ + const next = await chatEndpointsApi.update(endpoint.id, { executionDefaults }); + queryClient.setQueryData(queryKeys.chatEndpoints.detail(endpoint.id), next); + }} + />
; +} + function EmailAccess({ endpoint }: { endpoint: ChatEndpoint }) { const connection = useQuery({ queryKey: queryKeys.tools.connection(endpoint.connectionId ?? ""), @@ -508,7 +523,8 @@ function Settings({ ) : (
{destinationResources.map((resource) => ( -
+
+

{resource.label} @@ -533,6 +549,19 @@ function Settings({ } />

+
+ Task defaults +
{ + const next = await chatEndpointsApi.updateResources(endpointId, [{ id: resource.id, executionDefaults }]); + queryClient.setQueryData(queryKeys.chatEndpoints.resources(endpointId), next); + }} + />
+
+
))} {endpoint.provider === "slack" && (

diff --git a/ui/src/pages/apps/chat/ChatExecutionDefaults.test.tsx b/ui/src/pages/apps/chat/ChatExecutionDefaults.test.tsx new file mode 100644 index 00000000000..2d6f2048117 --- /dev/null +++ b/ui/src/pages/apps/chat/ChatExecutionDefaults.test.tsx @@ -0,0 +1,58 @@ +// @vitest-environment jsdom +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import { ChatExecutionDefaults } from "./ChatExecutionDefaults"; +const mocks = vi.hoisted(() => ({ projects: vi.fn(), workspaces: vi.fn(), save: vi.fn() })); +vi.mock("@/api/projects", () => ({ projectsApi: { list: mocks.projects } })); +vi.mock("@/api/execution-workspaces", () => ({ executionWorkspacesApi: { listSummaries: mocks.workspaces } })); +let root: Root, container: HTMLDivElement, client: QueryClient; +beforeEach(() => { + vi.clearAllMocks(); mocks.projects.mockResolvedValue([]); mocks.workspaces.mockResolvedValue([]); mocks.save.mockResolvedValue(undefined); + container = document.createElement("div"); document.body.append(container); root = createRoot(container); + client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); +}); +afterEach(async () => { await act(async () => root.unmount()); client.clear(); container.remove(); }); +async function render() { + await act(async () => root.render()); + await vi.waitFor(() => expect(container.querySelectorAll("select")[1].disabled).toBe(false)); +} +async function choose(value: string) { + await act(async () => { const select = container.querySelectorAll("select")[1]; select.value = value; select.dispatchEvent(new Event("change", { bubbles: true })); }); +} +async function save() { + await act(async () => container.querySelector("form")!.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }))); +} +it("lets a destination clear inherited workspace independently of project", async () => { + await render(); await choose("clear"); await save(); + expect(mocks.save).toHaveBeenCalledWith({ projectId: null, workspace: null }); + expect(container.textContent).toContain("Saved for new tasks."); +}); +it("keeps edits visible after a save failure and permits retry", async () => { + mocks.save.mockRejectedValueOnce(new Error("Workspace access was revoked")); + await render(); await choose("task"); await save(); + expect(container.querySelector('[role="alert"]')?.textContent).toContain("Workspace access was revoked"); + expect(container.querySelectorAll("select")[1].value).toBe("task"); + await save(); + expect(mocks.save).toHaveBeenLastCalledWith({ projectId: null, workspace: { kind: "task_directory" } }); + expect(container.textContent).toContain("Saved for new tasks."); +}); + +it("offers and saves an idle workspace from the shared task selection filter", async () => { + mocks.workspaces.mockResolvedValue([ + { id: "active-workspace", name: "Active files", status: "active", closedAt: null }, + { id: "idle-workspace", name: "Idle files", status: "idle", closedAt: null }, + ]); + await render(); + expect(mocks.workspaces).toHaveBeenCalledWith("company", { selectableForTask: true }); + const options = [...container.querySelectorAll("select")[1].options].map((option) => option.value); + expect(options).toContain("existing:active-workspace"); + expect(options).toContain("existing:idle-workspace"); + await choose("existing:idle-workspace"); + await save(); + expect(mocks.save).toHaveBeenCalledWith({ + projectId: null, + workspace: { kind: "existing", workspaceId: "idle-workspace" }, + }); +}); diff --git a/ui/src/pages/apps/chat/ChatExecutionDefaults.tsx b/ui/src/pages/apps/chat/ChatExecutionDefaults.tsx new file mode 100644 index 00000000000..cc60f61571c --- /dev/null +++ b/ui/src/pages/apps/chat/ChatExecutionDefaults.tsx @@ -0,0 +1,106 @@ +import { useId, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import type { ChatExecutionDefaults as Defaults, TaskWorkspaceSelection } from "@paperclipai/shared"; +import { projectsApi } from "@/api/projects"; +import { executionWorkspacesApi } from "@/api/execution-workspaces"; +import { Button } from "@/components/ui/button"; +import { NativeSelect } from "@/components/ui/select"; +import { queryKeys } from "@/lib/queryKeys"; + +function selectionKey(value: TaskWorkspaceSelection | null | undefined): string { + if (value === undefined) return "inherit"; + if (value === null) return "clear"; + if (value.kind === "task_directory") return "task"; + if (value.kind === "existing") return `existing:${value.workspaceId}`; + return `source:${value.projectWorkspaceId}:${value.mode}`; +} + +export function ChatExecutionDefaults({ companyId, value, resource = false, onSave }: { + companyId: string; + value: Defaults | null | undefined; + resource?: boolean; + onSave: (defaults: Defaults | null) => Promise; +}) { + const id = useId(); + const [draft, setDraft] = useState(null); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + const [saved, setSaved] = useState(false); + const projects = useQuery({ queryKey: queryKeys.projects.list(companyId), queryFn: () => projectsApi.list(companyId) }); + const workspaces = useQuery({ + queryKey: ["channel-workspace-choices", companyId], + queryFn: () => executionWorkspacesApi.listSummaries(companyId, { selectableForTask: true }), + }); + const current = draft ?? value ?? {}; + const projectValue = current.projectId === undefined ? "inherit" : current.projectId ?? "none"; + const workspaceValue = selectionKey(current.workspace); + const choices = new Map(); + choices.set("task", { kind: "task_directory" }); + for (const project of projects.data ?? []) { + for (const source of project.workspaces ?? []) { + for (const mode of ["managed_isolated", "shared"] as const) { + choices.set(`source:${source.id}:${mode}`, { kind: "configured_source", projectWorkspaceId: source.id, mode }); + } + } + } + for (const workspace of workspaces.data ?? []) { + choices.set(`existing:${workspace.id}`, { kind: "existing", workspaceId: workspace.id }); + } + const dirty = JSON.stringify(current) !== JSON.stringify(value ?? {}); + const change = (next: Defaults) => { setDraft(next); setSaved(false); setError(null); }; + return

{ + event.preventDefault(); + if (pending || !dirty) return; + setPending(true); setError(null); setSaved(false); + try { await onSave(Object.keys(current).length ? current : null); setDraft(null); setSaved(true); } + catch (cause) { setError(cause instanceof Error ? cause.message : "Couldn’t save task defaults. Try again."); } + finally { setPending(false); } + }}> +
+

Task defaults

+

Applies to new conversations. Existing tasks keep their files and project.

+
+ {(projects.isError || workspaces.isError) &&

Couldn’t load available projects or workspaces.

} +
+ + { + const { projectId: _previous, ...rest } = current; + change(event.target.value === "inherit" ? rest : { ...rest, projectId: event.target.value === "none" ? null : event.target.value }); + }}> + + + {current.projectId && !projects.data?.some((project) => project.id === current.projectId) && } + {(projects.data ?? []).map((project) => )} + +
+
+ + { + const key = event.target.value; + const { workspace: _previous, ...rest } = current; + if (key === "inherit") change(rest); + else if (key === "clear") change({ ...rest, workspace: null }); + else { const workspace = choices.get(key); if (workspace) change({ ...rest, workspace }); } + }}> + + {resource && } + {!resource && workspaceValue === "clear" && } + + {current.workspace && !choices.has(workspaceValue) && } + {(projects.data ?? []).filter((project) => project.workspaces?.length).map((project) => + {project.workspaces.flatMap((source) => [ + , + , + ])} + )} + {(workspaces.data?.length ?? 0) > 0 && {workspaces.data!.map((workspace) => )}} + +

Project and workspace access is checked for each task. A shared folder must also be allowed by the agent’s execution policy.

+
+ {error &&

{error}

} +
+
{dirty ? : saved ? Saved for new tasks. : null}
+ +
+
; +} diff --git a/ui/src/pages/apps/chat/GitHubBotManagement.test.tsx b/ui/src/pages/apps/chat/GitHubBotManagement.test.tsx index 231dbbf5f7a..78c713ff86f 100644 --- a/ui/src/pages/apps/chat/GitHubBotManagement.test.tsx +++ b/ui/src/pages/apps/chat/GitHubBotManagement.test.tsx @@ -8,6 +8,7 @@ import { buildSkillMentionHref, type GitHubChatConfiguration, type GitHubTaskReview, + type ChatExecutionDefaults, } from "@paperclipai/shared"; import { ChatEndpointDetail } from "./ChatEndpointDetail"; import { GitHubReviewList, orderedGitHubReviews } from "./GitHubBotManagement"; @@ -28,6 +29,8 @@ const mocks = vi.hoisted(() => ({ repositoryPage: vi.fn(), toggleAll: vi.fn(), updateResources: vi.fn(), + projects: vi.fn(), + workspaces: vi.fn(), reviews: vi.fn(), review: vi.fn(), members: vi.fn(), @@ -52,6 +55,8 @@ vi.mock("@/api/chatEndpoints", () => ({ listPrincipals: mocks.links, }, })); +vi.mock("@/api/projects", () => ({ projectsApi: { list: mocks.projects } })); +vi.mock("@/api/execution-workspaces", () => ({ executionWorkspacesApi: { listSummaries: mocks.workspaces } })); vi.mock("@/api/access", () => ({ accessApi: { listMembers: mocks.members } })); vi.mock("@/api/agents", () => ({ agentsApi: { get: vi.fn() } })); vi.mock("@/context/BreadcrumbContext", () => ({ @@ -136,6 +141,8 @@ describe("GitHub bot management", () => { mocks.tab = "settings"; mocks.reviewId = undefined; mocks.get.mockResolvedValue(endpoint); + mocks.projects.mockResolvedValue([]); + mocks.workspaces.mockResolvedValue([]); mocks.config.mockResolvedValue({ revision: 4, configuration: structuredClone(base), @@ -285,6 +292,76 @@ describe("GitHub bot management", () => { await vi.waitFor(() => expect(container.querySelectorAll('[role="region"] [role="switch"]')).toHaveLength(1)); expect(container.textContent).not.toContain("Could not load repositories"); }); + it("saves repository task defaults with explicit, cleared, and inherited values without changing access", async () => { + let executionDefaults: ChatExecutionDefaults | null = null; + mocks.projects.mockResolvedValue([{ id: "project", name: "Website", workspaces: [] }]); + mocks.workspaces.mockResolvedValue([{ id: "workspace", name: "Shared website" }]); + mocks.resources.mockImplementation(async () => [{ + id: "repo", label: "acme/web", type: "repository", enabled: false, + availability: "available", executionDefaults, + }]); + mocks.updateResources.mockImplementation(async (_id, [update]) => { + executionDefaults = update.executionDefaults; + return mocks.resources(); + }); + await render("access"); + await vi.waitFor(() => expect(container.querySelector('[role="region"] summary')).not.toBeNull()); + await act(async () => (container.querySelector('[role="region"] summary') as HTMLElement).click()); + const editor = container.querySelector('[role="region"] form')!; + const [project, workspace] = [...editor.querySelectorAll("select")]; + await vi.waitFor(() => expect(workspace.disabled).toBe(false)); + expect(mocks.projects).toHaveBeenCalledWith("company"); + expect(mocks.workspaces).toHaveBeenCalledWith("company", { selectableForTask: true }); + expect(project.value).toBe("inherit"); + expect(workspace.value).toBe("inherit"); + expect(editor.textContent).toContain("Use connection defaults"); + + const save = async (expected: ChatExecutionDefaults | null) => { + const calls = mocks.updateResources.mock.calls.length; + await click("Save defaults"); + await vi.waitFor(() => { + expect(mocks.updateResources).toHaveBeenCalledTimes(calls + 1); + expect(editor.textContent).toContain("Saved for new tasks."); + }); + expect(mocks.updateResources).toHaveBeenLastCalledWith("bot", [{ id: "repo", executionDefaults: expected }]); + expect(container.querySelector('[role="region"] [role="switch"]')?.getAttribute("aria-checked")).toBe("false"); + }; + await input(project, "project"); + await input(workspace, "existing:workspace"); + await save({ projectId: "project", workspace: { kind: "existing", workspaceId: "workspace" } }); + expect(project.value).toBe("project"); + expect(workspace.value).toBe("existing:workspace"); + await input(project, "none"); + await input(workspace, "clear"); + await save({ projectId: null, workspace: null }); + await input(project, "inherit"); + await save({ workspace: null }); + await input(workspace, "inherit"); + await save(null); + await vi.waitFor(() => { + expect(project.value).toBe("inherit"); + expect(workspace.value).toBe("inherit"); + }); + expect(mocks.save).not.toHaveBeenCalled(); + expect(mocks.toggleAll).not.toHaveBeenCalled(); + }); + it("keeps a rejected repository task-defaults draft visible for retry", async () => { + mocks.updateResources.mockRejectedValueOnce(new Error("Workspace access denied")); + await render("access"); + await vi.waitFor(() => expect(container.querySelector('[role="region"] summary')).not.toBeNull()); + await act(async () => (container.querySelector('[role="region"] summary') as HTMLElement).click()); + const editor = container.querySelector('[role="region"] form')!; + const workspace = editor.querySelectorAll("select")[1]; + await vi.waitFor(() => expect(workspace.disabled).toBe(false)); + await input(workspace, "task"); + await click("Save defaults"); + await vi.waitFor(() => expect(editor.querySelector('[role="alert"]')?.textContent).toBe("Workspace access denied")); + expect(workspace.value).toBe("task"); + expect(container.querySelector('[role="region"] [role="switch"]')?.getAttribute("aria-checked")).toBe("true"); + await click("Cancel"); + expect(workspace.value).toBe("inherit"); + expect(editor.querySelector('[role="alert"]')).toBeNull(); + }); it("automatically loads the next 20 repositories when the scroll sentinel becomes visible", async () => { let intersect!: IntersectionObserverCallback; vi.stubGlobal("IntersectionObserver", class { diff --git a/ui/src/pages/apps/chat/GitHubBotManagement.tsx b/ui/src/pages/apps/chat/GitHubBotManagement.tsx index 694a2d671ab..564869fed47 100644 --- a/ui/src/pages/apps/chat/GitHubBotManagement.tsx +++ b/ui/src/pages/apps/chat/GitHubBotManagement.tsx @@ -36,9 +36,11 @@ import { GitHubPolicyEditor, GitHubToggle, } from "./GitHubBotConfiguration"; +import { ChatExecutionDefaults } from "./ChatExecutionDefaults"; export function GitHubRepositoryAccess({ endpointId, + companyId, managementUrl, pending, onRefresh, @@ -46,12 +48,14 @@ export function GitHubRepositoryAccess({ onToggleAll, }: { endpointId: string; + companyId: string; managementUrl: string; pending: boolean; onRefresh: () => void; onChange: (id: string, enabled: boolean) => void; onToggleAll: (enabled: boolean) => void; }) { + const client = useQueryClient(); const [search, setSearch] = useState(""); const scroller = useRef(null); const more = useRef(null); @@ -144,6 +148,21 @@ export function GitHubRepositoryAccess({ } onChange={(enabled) => onChange(resource.id, enabled)} /> +
+ Task defaults +
+ { + await chatEndpointsApi.updateResources(endpointId, [{ id: resource.id, executionDefaults }]); + await client.invalidateQueries({ queryKey: ["github-bot-repositories", endpointId] }); + await client.invalidateQueries({ queryKey: ["github-bot-repository-pages", endpointId] }); + }} + /> +
+
))}
@@ -241,6 +260,7 @@ export function GitHubBotManagement({ <> Response.json(data); if (path === `/api/agents/${agent.id}`) return json(agent); if (path === "/api/companies/company-storybook/agents") return json([agent, ...storybookAgents.slice(1)]); + if (path === "/api/companies/company-storybook/execution-workspaces" && url.searchParams.get("selectableForTask") === "true") { + return json(storybookExecutionWorkspaces.filter(workspace => + workspace.closedAt == null && ["active", "idle"].includes(workspace.status))); + } if (path === "/api/chat-endpoints/chat-management") { if (init?.method === "PATCH") endpoint = { ...endpoint, ...JSON.parse(String(init.body)) }; return json(endpoint); diff --git a/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx b/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx index 38b2ed87ee0..442423df956 100644 --- a/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx +++ b/ui/storybook/stories/chat-channels/github-bot-management/management.stories.tsx @@ -197,6 +197,7 @@ function RepositoryExample() { return ( {}} diff --git a/ui/storybook/support/github-bot-management/fixture-api.test.tsx b/ui/storybook/support/github-bot-management/fixture-api.test.tsx index 65a2fc2dc2a..881f4644a2b 100644 --- a/ui/storybook/support/github-bot-management/fixture-api.test.tsx +++ b/ui/storybook/support/github-bot-management/fixture-api.test.tsx @@ -30,3 +30,26 @@ it("serves a review detail to the routed preview and rejects unknown review IDs" flushSync(() => root.unmount()); } }); + +it("persists connection defaults, preserves omitted values and clears them explicitly", async () => { + vi.spyOn(window, "fetch").mockResolvedValue(Response.json({})); + let root = createRoot(document.createElement("div")); + const url = `/api/chat-endpoints/${endpoint.id}`; + const patch = async (body: unknown) => (await window.fetch(url, { + method: "PATCH", body: JSON.stringify(body), + })).json(); + try { + flushSync(() => root.render(Ready)); + const executionDefaults = { projectId: null, workspace: { kind: "task_directory" } }; + expect(await patch({ executionDefaults })).toMatchObject({ executionDefaults }); + expect(await (await window.fetch(url)).json()).toMatchObject({ executionDefaults }); + expect(await patch({ communicationInstructions: "Fixture instructions" })).toMatchObject({ executionDefaults }); + expect(await patch({ executionDefaults: null })).toMatchObject({ executionDefaults: null }); + flushSync(() => root.unmount()); + root = createRoot(document.createElement("div")); + flushSync(() => root.render(Ready)); + expect((await (await window.fetch(url)).json()).executionDefaults).toEqual(endpoint.executionDefaults); + } finally { + flushSync(() => root.unmount()); + } +}); diff --git a/ui/storybook/support/github-bot-management/fixture-api.tsx b/ui/storybook/support/github-bot-management/fixture-api.tsx index fce58a97ea2..b826997e060 100644 --- a/ui/storybook/support/github-bot-management/fixture-api.tsx +++ b/ui/storybook/support/github-bot-management/fixture-api.tsx @@ -1,7 +1,7 @@ import { useLayoutEffect, useState, type ReactNode } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { queryKeys } from "@/lib/queryKeys"; -import { buildSkillMentionHref } from "@paperclipai/shared"; +import { buildSkillMentionHref, type ChatExecutionDefaults } from "@paperclipai/shared"; import { EditorAutocompleteProvider } from "@/context/EditorAutocompleteContext"; import { endpoint, @@ -37,6 +37,8 @@ export function FixtureApi({ ? { ...endpoint, status: "draft", setup: { github: { stage: "setup" } } } : endpoint); client.setQueryData(["github-wizard", endpoint.id], { endpointId: endpoint.id, state: state === "setup" ? "create" : "connected" }); client.setQueryData(queryKeys.chatEndpoints.list(endpoint.companyId), [endpoint]); + client.setQueryData(queryKeys.projects.list(endpoint.companyId), []); + client.setQueryData(["channel-workspace-choices", endpoint.companyId], []); client.setQueryData(["project-repositories", endpoint.companyId], { repositories: [ { id: "100", fullName: "acme/web", ownerType: "organization", url: "https://github.com/acme/web", connections: ["GitHub"] }, @@ -51,6 +53,9 @@ export function FixtureApi({ const [ready, setReady] = useState(false); useLayoutEffect(() => { const original = window.fetch; + let currentEndpoint = state === "setup" + ? { ...endpoint, status: "draft", setup: { github: { stage: "setup" } } } + : structuredClone(endpoint); let saved = { revision: 2, configuration: structuredClone(configuration) }; if (state === "skills") saved.configuration.defaults = { ...saved.configuration.defaults, @@ -90,6 +95,9 @@ export function FixtureApi({ ); } const body = init?.body ? JSON.parse(String(init.body)) : {}; + if (path === `/api/chat-endpoints/${endpoint.id}` && init?.method === "PATCH") { + currentEndpoint = { ...currentEndpoint, ...body }; + } if (path.endsWith("/github/setup")) return Response.json({ endpointId: endpoint.id, state: state === "setup" ? "create" : "connected" }); if (path.endsWith("/repositories/access")) { repos = repos.map((r) => (!body.enabled || r.availability === "available") ? { ...r, enabled: body.enabled } : r); @@ -118,14 +126,16 @@ export function FixtureApi({ if (path.endsWith("/repositories/refresh")) return Response.json(repos); if (path.endsWith("/resources")) { if (init?.method === "PUT") - repos = repos.map((r) => ({ - ...r, - enabled: - body.resources?.find( - (change: { id: string; enabled: boolean }) => - change.id === r.id, - )?.enabled ?? r.enabled, - })); + repos = repos.map((r) => { + const change = body.resources?.find( + (entry: { id: string; enabled?: boolean; executionDefaults?: ChatExecutionDefaults | null }) => entry.id === r.id, + ); + return { + ...r, + enabled: change?.enabled ?? r.enabled, + executionDefaults: change?.executionDefaults === undefined ? r.executionDefaults : change.executionDefaults, + }; + }); const rows = state === "empty" ? [] @@ -168,7 +178,7 @@ export function FixtureApi({ } if (path.endsWith("/people/lookup")) return Response.json({ githubUserId: "44", login: body.login }); - return Response.json(state === "setup" ? { ...endpoint, status: "draft", setup: { github: { stage: "setup" } } } : endpoint); + return Response.json(currentEndpoint); }; setReady(true); return () => { From 83e13db8fa52b40fea34534f7237c231d0874c23 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:25:15 -0700 Subject: [PATCH 13/37] fix(inbox): keep automatic recovery cleanup out of personal participation (#16063) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The personal inbox uses issue activity to identify a person's participation. > - Issue reads can cancel stale recovery actions and record that cleanup as the reader's action. > - This adds unrelated tasks to the inbox and refreshes their activity timestamps. > - A workspace owner hold can also alternate between creation and cancellation after a task finishes. > - This pull request corrects cleanup attribution, excludes historical read cleanup, and retains one exact owner hold. > - Agents can read authorized issue activity to diagnose the same problem themselves. ## Linked Issues or Issue Description Refs #15742 excludes logs by the execution-recovery actor from activity timestamps. This PR covers reader-attributed stale recovery cleanup, personal inbox participation, exact workspace owner holds, and sandbox issue activity access. I searched open issues and PRs for recovery inbox and activity bridge fixes. I found no duplicate for this defect. **What happened?** Loading issue lists cancels stale recovery actions under the reader's identity. The personal inbox treats those rows as user participation. One completed issue had hundreds of such rows and no edits or comments by the affected reader. A retained native workspace owner repeatedly creates a verification action that read projections then cancel. Sandbox agents cannot inspect the issue activity because the reverse bridge rejects the route. **Expected behavior** Automatic cleanup belongs to the system and does not add an issue to a reader's inbox. Historical cleanup rows remain auditable but do not count as participation or recent activity. An unresolved physical workspace owner keeps one stable verification action. Agents can inspect activity only for issues their identity may read. **Steps to reproduce** 1. Finish a task whose native run retains an unverifiable workspace owner receipt. 2. Repeat workspace recovery checks and GET requests for issue detail, compact lists, and recovery actions. 3. Observe new recovery action IDs and reader-attributed cancellations in the activity history. 4. Query the personal inbox for that reader and observe the unrelated task. 5. From a sandbox agent, request GET /api/issues/:id/activity and observe Route not allowed. **Paperclip version or commit** Observed at f5346c05b923a1eb1d15d04d8e08b993a7c4d097. Regression tests use current master behavior. **Deployment mode** Authenticated hosted instance with native and legacy sandbox agents. The inbox defect is independent of the adapter. ## What Changed - Attribute automatic stale recovery cleanup to the recovery system actor. - Exclude exact historical read cleanup rows from personal inbox membership, unread counts, and both activity timestamp queries. Keep all audit rows and explicit user recovery decisions. - Retain owner verification holds only while the exact company, run, issue, and owner fingerprint still match. Apply the same rule to reads and background recovery. - Make repeated checks for the same owner hold leave its identity, attempts, and timestamps unchanged. Keep physical takeover blocked. - Allow GET issue activity through the sandbox bridge. Keep the existing company and issue-read authorization and reject broader audit routes. - Add regressions and update the runtime API and recovery documentation. Refresh generated source references for the documentation edits. Update the paired activity, inbox, and recovery maps. ## Verification - Focused Vitest verification: 315 tests pass across five files. The updated terminal owner hold regression also passes. The files cover issue service, recovery routes, activity authorization, sandbox callback bridge, and native workspace finalization. - `pnpm -r typecheck` and `pnpm build` pass locally. The full general and serialized unit test matrix passes in CI on the latest commit. The duplicate local `pnpm test:run` was stopped after about 30 minutes once that full CI coverage passed; it did not report a local full-suite pass. - `node scripts/check-maps.mjs` passes for all paired areas and capability IDs. - Apex is 5/5 on commit 44c3787888c2efb689543c9f0ca3345175df7bb4. Its access map finding is fixed and resolved. All CI checks are green on this commit. - The new regression repeats reads and background sweeps for done and cancelled tasks. It checks one unchanged hold, zero physical copyback, zero personal inbox participation, and system cleanup after the exact receipt is cleared. - Historical cleanup tests preserve explicit recovery decisions, nullable metadata, unread counts, and the original audit rows. - Authorization tests permit an authorized agent and reject cross-company or issue-boundary reads. Bridge tests reject write methods, extra path segments, and company-wide audit routes. ## Risks Existing inbox items can disappear if their only participation evidence was automatic read cleanup. No data migration or audit deletion is needed. An unresolved workspace owner remains visible after task completion; this preserves the physical writer fence. A stale or cleared owner does not retain the hold. Issue activity access expands only the sandbox transport allowlist and still uses existing controller authorization. ## Model Used OpenAI GPT-6 through Codex, with reasoning, repository inspection, code editing, shell execution, and test tools. The session identifies the model family as GPT-6. A more specific runtime model ID and context window size are not exposed. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip Co-authored-by: Amy (Opus) --- doc/native-workspace-finalization-recovery.md | 9 + maps/agent-access-map/activity.md | 32 +- maps/agent-access-map/inbox-search.md | 15 +- maps/agent-access-map/recovery.md | 16 +- maps/feature-map/activity.md | 6 + maps/feature-map/inbox-search.md | 11 + maps/feature-map/recovery.md | 8 + .../src/sandbox-callback-bridge.test.ts | 5 + .../src/sandbox-callback-bridge.ts | 2 + .../docs/capability-contract.md | 188 ++--- .../generated/capability/capabilities.yaml | 404 +++++------ .../capability/capability-contract.md | 2 +- .../spec/capability/capabilities.yaml | 686 +++++++++--------- server/src/__tests__/activity-routes.test.ts | 27 + .../heartbeat-process-recovery.test.ts | 9 +- .../__tests__/issue-recovery-actions.test.ts | 47 ++ server/src/__tests__/issues-service.test.ts | 33 + server/src/routes/issues.ts | 14 +- server/src/services/issues.ts | 13 + ...native-workspace-finalization-ownership.ts | 14 +- ...e-workspace-finalization-recovery-state.ts | 24 + server/src/services/recovery/service.ts | 5 + skills/paperclip/SKILL.md | 3 + skills/paperclip/references/api-reference.md | 6 + 24 files changed, 913 insertions(+), 666 deletions(-) create mode 100644 server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts diff --git a/doc/native-workspace-finalization-recovery.md b/doc/native-workspace-finalization-recovery.md index 2be7f1b67db..01201315417 100644 --- a/doc/native-workspace-finalization-recovery.md +++ b/doc/native-workspace-finalization-recovery.md @@ -9,6 +9,15 @@ A completed workspace barrier is reread under ownership before export, and a committed coordinator cannot be overwritten by a late failure receipt. +An unverified workspace owner keeps one board recovery action for its exact run +and owner fingerprint. Repeated recovery checks do not consume attempts or +rewrite that action. Task completion or cancellation does not prove the old +physical writer stopped, so reads and recovery sweeps retain this action while +the matching run still carries that owner receipt. After the receipt is cleared +or replaced, ordinary stale-action cleanup applies. Cleanup is attributed to +the system; historical cleanup from page reads does not add tasks to a person's +inbox or refresh issue activity timestamps. + The lock transaction holds no row locks. Ordinary progress and finalization receipts remain visible through the normal database pool. A dedicated connection outside the application pool is reserved for the duration of copyback and closed when it settles; even a one-connection application pool remains available for progress writes. Its run profile carries diff --git a/maps/agent-access-map/activity.md b/maps/agent-access-map/activity.md index b5d2041289c..40d61378fdd 100644 --- a/maps/agent-access-map/activity.md +++ b/maps/agent-access-map/activity.md @@ -40,22 +40,32 @@ Review: `baseline`. API: `present` — representative operation IDs: - `GET /api/companies/{companyId}/activity`. +- `GET /api/issues/{id}/activity` (UUID or issue identifier). -Agent access: `available` — REST / native call_api / CLI activity. +Agent access: `partial` — REST / native call_api / CLI activity for company +activity. Sandbox agents can use GET issue activity through the default +[callback bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.ts). +The bridge still rejects company activity and company audit routes. -Policy: Same company plus visibility/audit/cost/run permissions where the route requires -them. +Policy: Company activity requires same-company access and `company_scope:read`, +with issue visibility filtering. Issue activity requires same-company resource +access and `issue:read` for the resolved issue. The sandbox transport does not +replace those controller checks or permit activity writes. -Evidence: `source-only` — [implementation](../../server/src/routes/activity.ts#L224) at -the reviewed commit above. +Evidence: `source-only` — [activity routes](../../server/src/routes/activity.ts), +[agent authorization tests](../../server/src/__tests__/activity-routes.test.ts), +and [bridge tests](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts). +Issue activity transport reviewed at `89770ba707fb1ebd4cabd184eec2e7fee226e0c3`; +other operations retain the baseline source review above. -Verification: `not-run` — exercise a listed permitted operation with actual agent -credentials/run context; inspect its result and applicable persistence/attribution. -Assert the relevant denied actor, owner, company or mode from Policy. Review the -remaining operations before qualifying the whole capability. +Verification: `not-run` — no live agent-credential proof. Local route and bridge +regressions pass: permitted agent issue read, denied company/resource scope, +denied writes, extra path segments, and company audit routes. With a real +sandbox agent, read an authorized issue's activity, then verify denial for an +inaccessible issue, another company, and company-wide audit. -Gaps: Audit export and raw activity creation have extra restrictions; do not equate them -with activity reading. +Gaps: Audit export and raw activity creation have extra restrictions; do not +equate them with issue activity reading. Live sandbox access remains unverified. ## `runs` diff --git a/maps/agent-access-map/inbox-search.md b/maps/agent-access-map/inbox-search.md index 2c079231dd9..84ada459d12 100644 --- a/maps/agent-access-map/inbox-search.md +++ b/maps/agent-access-map/inbox-search.md @@ -22,15 +22,24 @@ API: `present` — representative operation IDs: Agent access: `partial` — REST / native call_api; MCP inbox/task search. Policy: Own agent context; responsible-user inbox policy and explicit opt-in/grants for -cross-user archive; decision policy separately. +cross-user archive; decision policy separately. Automatic read cleanup does not +count as user participation, including historical reader-attributed rows. +Explicit user recovery decisions still count. Evidence: `source-only` — [implementation](../../server/src/routes/agents.ts#L3952) at -the reviewed commit above. +the reviewed commit above. The cleanup exclusion in +[issue queries](../../server/src/services/issues.ts) was reviewed at +`89770ba707fb1ebd4cabd184eec2e7fee226e0c3`. +[Issue service regressions](../../server/src/__tests__/issues-service.test.ts) +cover membership, unread counts, timestamps, and retained audit rows. Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the -remaining operations before qualifying the whole capability. +remaining operations before qualifying the whole capability. Seed a completed +issue with only automatic read cleanup under a user identity: Mine and Unread +must exclude it while activity history retains the row. An explicit recovery +decision must still establish participation. No live user-inbox proof is claimed. Gaps: Agent inbox and Mine support exist; board attention/unread/dismissal aggregation is a separate user-only surface. diff --git a/maps/agent-access-map/recovery.md b/maps/agent-access-map/recovery.md index a6a12a3f1c0..db98fb8ef76 100644 --- a/maps/agent-access-map/recovery.md +++ b/maps/agent-access-map/recovery.md @@ -64,20 +64,30 @@ Review: `baseline`. API: `present` — representative operation IDs: +- `GET /api/issues/{id}/recovery-actions`. - `POST /api/issues/{id}/recovery-actions/resolve`. Agent access: `partial` — Eligible agent REST / CLI; source-bound recovery state. Policy: Task/run visibility, run ownership and source/action policy; workspace export -retry and some operator controls require board. +retry and some operator controls require board. Completion or cancellation of a +task does not clear its exact unresolved native workspace owner hold. Physical +owner release still requires the [operator stop-verification procedure](../../doc/native-workspace-finalization-recovery.md). Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts#L9478) at -the reviewed commit above. +the reviewed commit above. Exact owner hold retention in the +[recovery state helper](../../server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts) +was reviewed at `89770ba707fb1ebd4cabd184eec2e7fee226e0c3`. Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the -remaining operations before qualifying the whole capability. +remaining operations before qualifying the whole capability. Local +[recovery route regressions](../../server/src/__tests__/issue-recovery-actions.test.ts) +verify one unchanged hold across reads and sweeps on done/cancelled tasks, no +physical copyback, and cleanup after the receipt is cleared. Exercise this +sequence in a disposable instance; automatic cleanup must use the system actor +and must not create reader participation. No live recovery proof is claimed. Gaps: Need operation-level eligibility and a real native binding for permitted recovery actions; do not route around lifecycle authority. diff --git a/maps/feature-map/activity.md b/maps/feature-map/activity.md index 0a80901ddfa..49f9489906f 100644 --- a/maps/feature-map/activity.md +++ b/maps/feature-map/activity.md @@ -40,6 +40,12 @@ Automated: [activity service](../../server/src/__tests__/activity-service.test.t Manual: Find a known mutation by actor/resource and a known run by status. Change filters, load more, and follow the source links. Reload the chosen view and verify no company leakage or duplicate events. Inspect a routine run from its audit entry. +Issue activity is also available through `GET /api/issues/{id}/activity`. +[Agent authorization](../../server/src/__tests__/activity-routes.test.ts) and +[sandbox bridge](../../packages/adapter-utils/src/sandbox-callback-bridge.test.ts) +regressions cover this route separately from company-wide audit. Live sandbox +agent access remains unverified; use the companion recipe. + Audit-event Load more and run listings differ: Runs currently loads at most 200 entries without pagination. The tested RoutineAuditActivity component has no registered activity-route host; inspect execution links actually offered by diff --git a/maps/feature-map/inbox-search.md b/maps/feature-map/inbox-search.md index ac3570b5c38..14df9295962 100644 --- a/maps/feature-map/inbox-search.md +++ b/maps/feature-map/inbox-search.md @@ -35,8 +35,19 @@ Preconditions: follow the [baseline](./README.md#before-driving-a-journey). Seed Automated: [archive routes](../../server/src/__tests__/inbox-archive-routes.test.ts) cover server mutations. [Task-list helpers](../../ui/src/pages/Issues.test.tsx) cover search URLs, pagination, and presentation constants; they do not render the combined Inbox/Tasks page. Reading, archiving, and navigating that page remain manual checks. +Automated: [Issue service regressions](../../server/src/__tests__/issues-service.test.ts) +cover exclusion of historical automatic read cleanup from user participation, +unread counts, and activity timestamps while retaining explicit user decisions +and audit history. + Manual: Read an unread task, return to Unread, archive a handled item, and reload. Verify personal triage state changes while task status stays correct. Switch users and companies to check isolation. +Seed a completed task whose only activity under the viewing user is automatic +read cleanup. Refresh Mine and Unread: the task must not appear through that +participation, while the cleanup remains in activity history. Record an explicit +user recovery decision on a separate task and verify it still counts. This +browser journey remains manual. + ### `decisions` Automated: [decision queues](../../server/src/__tests__/decision-queues-routes.test.ts) covers route behavior. End-to-end queue-to-source navigation is manual. diff --git a/maps/feature-map/recovery.md b/maps/feature-map/recovery.md index 0236b1e4dec..3cd71abaffc 100644 --- a/maps/feature-map/recovery.md +++ b/maps/feature-map/recovery.md @@ -90,6 +90,14 @@ the supported recovery until the source task produces a useful result or a specific escalation. Reload and verify that resolved notices retire and attempt history remains. A fixture pass does not qualify every live harness/provider. +For a native workspace owner hold, repeat issue reads and recovery checks after +the task becomes done or cancelled. Verify one unchanged action ID, attempt +count, and timestamps; task completion must not authorize copyback. Clear only +the exact receipt after operator stop verification, then confirm stale action +cleanup. The [route regressions](../../server/src/__tests__/issue-recovery-actions.test.ts) +and [heartbeat regression](../../server/src/__tests__/heartbeat-process-recovery.test.ts) +cover this sequence without a live operator recovery claim. + ### `run-detail` Automated: [run workspace recovery](../../ui/src/components/RunWorkspaceRecoverySurface.test.tsx) diff --git a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts index c81f162ea32..e7e639bd0a8 100644 --- a/packages/adapter-utils/src/sandbox-callback-bridge.test.ts +++ b/packages/adapter-utils/src/sandbox-callback-bridge.test.ts @@ -1500,6 +1500,7 @@ describe("sandbox callback bridge", () => { { method: "GET", path: "/api/goals/goal-1" }, { method: "GET", path: "/api/issues/issue-1" }, { method: "GET", path: "/api/issues/issue-1/heartbeat-context" }, + { method: "GET", path: "/api/issues/issue-1/activity" }, { method: "GET", path: "/api/issues/issue-1/comments" }, { method: "GET", path: "/api/issues/issue-1/comments/c-1" }, { method: "POST", path: "/api/issues/issue-1/comments" }, @@ -1546,6 +1547,10 @@ describe("sandbox callback bridge", () => { } const denied: Array<{ method: string; path: string }> = [ + { method: "POST", path: "/api/issues/issue-1/activity" }, + { method: "GET", path: "/api/issues/issue-1/activity/extra" }, + { method: "GET", path: "/api/companies/co-1/activity" }, + { method: "GET", path: "/api/companies/co-1/audit/agent-actions" }, { method: "GET", path: "/api/companies/co-1/agent-commentary" }, { method: "POST", path: "/api/companies/co-1/agent-commentary/other" }, { method: "POST", path: "/api/companies/co-1/email/inboxes" }, diff --git a/packages/adapter-utils/src/sandbox-callback-bridge.ts b/packages/adapter-utils/src/sandbox-callback-bridge.ts index 64bf04a3940..6a392f51842 100644 --- a/packages/adapter-utils/src/sandbox-callback-bridge.ts +++ b/packages/adapter-utils/src/sandbox-callback-bridge.ts @@ -162,6 +162,8 @@ export const DEFAULT_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST: readonly SandboxCa // Issue lifecycle: read context, checkout, update, comment, document, release { method: "GET", path: /^\/api\/issues\/[^/]+$/ }, { method: "GET", path: /^\/api\/issues\/[^/]+\/heartbeat-context$/ }, + // The controller still checks company membership and issue read authority. + { method: "GET", path: /^\/api\/issues\/[^/]+\/activity$/ }, { method: "GET", path: /^\/api\/issues\/[^/]+\/comments(?:\/[^/]+)?$/ }, { method: "POST", path: /^\/api\/issues\/[^/]+\/comments$/ }, { method: "GET", path: /^\/api\/issues\/[^/]+\/documents(?:\/[^/]+)?$/ }, diff --git a/packages/paperclip-runner/docs/capability-contract.md b/packages/paperclip-runner/docs/capability-contract.md index ba41e93259b..1f71df88072 100644 --- a/packages/paperclip-runner/docs/capability-contract.md +++ b/packages/paperclip-runner/docs/capability-contract.md @@ -47,104 +47,104 @@ The skill/reference inventory and eval cases are the only normative behavior sou | skill:skills/paperclip/SKILL.md:conversation-tasks:32 | optional_agent_tool | skills/paperclip/SKILL.md:32 | | skill:skills/paperclip/SKILL.md:server-verified-external-chat-turns:49 | control_plane_owned | skills/paperclip/SKILL.md:49 | | skill:skills/paperclip/SKILL.md:the-heartbeat-procedure:89 | optional_agent_tool | skills/paperclip/SKILL.md:89 | -| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:198 | always_agent_tool | skills/paperclip/SKILL.md:198 | -| skill:skills/paperclip/SKILL.md:status-quick-guide:246 | control_plane_owned | skills/paperclip/SKILL.md:246 | -| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:256 | optional_agent_tool | skills/paperclip/SKILL.md:256 | -| skill:skills/paperclip/SKILL.md:delegating-review-tasks:269 | always_agent_tool | skills/paperclip/SKILL.md:269 | -| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:280 | control_plane_owned | skills/paperclip/SKILL.md:280 | -| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:288 | control_plane_owned | skills/paperclip/SKILL.md:288 | -| skill:skills/paperclip/SKILL.md:requesting-board-approval:313 | optional_agent_tool | skills/paperclip/SKILL.md:313 | -| skill:skills/paperclip/SKILL.md:issue-thread-interactions:334 | optional_agent_tool | skills/paperclip/SKILL.md:334 | -| skill:skills/paperclip/SKILL.md:standalone-decisions:364 | optional_agent_tool | skills/paperclip/SKILL.md:364 | -| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:468 | optional_agent_tool | skills/paperclip/SKILL.md:468 | -| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:510 | optional_agent_tool | skills/paperclip/SKILL.md:510 | -| skill:skills/paperclip/SKILL.md:cases:520 | optional_agent_tool | skills/paperclip/SKILL.md:520 | -| skill:skills/paperclip/SKILL.md:company-skills-workflow:525 | optional_agent_tool | skills/paperclip/SKILL.md:525 | -| skill:skills/paperclip/SKILL.md:routines:536 | optional_agent_tool | skills/paperclip/SKILL.md:536 | -| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547 | optional_agent_tool | skills/paperclip/SKILL.md:547 | -| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553 | optional_agent_tool | skills/paperclip/SKILL.md:553 | -| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560 | optional_agent_tool | skills/paperclip/SKILL.md:560 | -| skill:skills/paperclip/SKILL.md:reading-granted-secrets:567 | optional_agent_tool | skills/paperclip/SKILL.md:567 | -| skill:skills/paperclip/SKILL.md:critical-rules:593 | optional_agent_tool | skills/paperclip/SKILL.md:593 | -| skill:skills/paperclip/SKILL.md:comment-style-required:614 | always_agent_tool | skills/paperclip/SKILL.md:614 | -| skill:skills/paperclip/SKILL.md:update:646 | optional_agent_tool | skills/paperclip/SKILL.md:646 | -| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656 | always_agent_tool | skills/paperclip/SKILL.md:656 | -| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670 | optional_agent_tool | skills/paperclip/SKILL.md:670 | -| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701 | optional_agent_tool | skills/paperclip/SKILL.md:701 | -| skill:skills/paperclip/SKILL.md:searching-issues:730 | optional_agent_tool | skills/paperclip/SKILL.md:730 | -| skill:skills/paperclip/SKILL.md:full-reference:740 | optional_agent_tool | skills/paperclip/SKILL.md:740 | -| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744 | always_agent_tool | skills/paperclip/SKILL.md:744 | -| skill:skills/paperclip/SKILL.md:incidental-feedback:761 | optional_agent_tool | skills/paperclip/SKILL.md:761 | +| skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200 | always_agent_tool | skills/paperclip/SKILL.md:200 | +| skill:skills/paperclip/SKILL.md:status-quick-guide:248 | control_plane_owned | skills/paperclip/SKILL.md:248 | +| skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258 | optional_agent_tool | skills/paperclip/SKILL.md:258 | +| skill:skills/paperclip/SKILL.md:delegating-review-tasks:271 | always_agent_tool | skills/paperclip/SKILL.md:271 | +| skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282 | control_plane_owned | skills/paperclip/SKILL.md:282 | +| skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290 | control_plane_owned | skills/paperclip/SKILL.md:290 | +| skill:skills/paperclip/SKILL.md:requesting-board-approval:315 | optional_agent_tool | skills/paperclip/SKILL.md:315 | +| skill:skills/paperclip/SKILL.md:issue-thread-interactions:336 | optional_agent_tool | skills/paperclip/SKILL.md:336 | +| skill:skills/paperclip/SKILL.md:standalone-decisions:366 | optional_agent_tool | skills/paperclip/SKILL.md:366 | +| skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470 | optional_agent_tool | skills/paperclip/SKILL.md:470 | +| skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512 | optional_agent_tool | skills/paperclip/SKILL.md:512 | +| skill:skills/paperclip/SKILL.md:cases:522 | optional_agent_tool | skills/paperclip/SKILL.md:522 | +| skill:skills/paperclip/SKILL.md:company-skills-workflow:527 | optional_agent_tool | skills/paperclip/SKILL.md:527 | +| skill:skills/paperclip/SKILL.md:routines:538 | optional_agent_tool | skills/paperclip/SKILL.md:538 | +| skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549 | optional_agent_tool | skills/paperclip/SKILL.md:549 | +| skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555 | optional_agent_tool | skills/paperclip/SKILL.md:555 | +| skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562 | optional_agent_tool | skills/paperclip/SKILL.md:562 | +| skill:skills/paperclip/SKILL.md:reading-granted-secrets:569 | optional_agent_tool | skills/paperclip/SKILL.md:569 | +| skill:skills/paperclip/SKILL.md:critical-rules:595 | optional_agent_tool | skills/paperclip/SKILL.md:595 | +| skill:skills/paperclip/SKILL.md:comment-style-required:616 | always_agent_tool | skills/paperclip/SKILL.md:616 | +| skill:skills/paperclip/SKILL.md:update:648 | optional_agent_tool | skills/paperclip/SKILL.md:648 | +| skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658 | always_agent_tool | skills/paperclip/SKILL.md:658 | +| skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672 | optional_agent_tool | skills/paperclip/SKILL.md:672 | +| skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703 | optional_agent_tool | skills/paperclip/SKILL.md:703 | +| skill:skills/paperclip/SKILL.md:searching-issues:733 | optional_agent_tool | skills/paperclip/SKILL.md:733 | +| skill:skills/paperclip/SKILL.md:full-reference:743 | optional_agent_tool | skills/paperclip/SKILL.md:743 | +| skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747 | always_agent_tool | skills/paperclip/SKILL.md:747 | +| skill:skills/paperclip/SKILL.md:incidental-feedback:764 | optional_agent_tool | skills/paperclip/SKILL.md:764 | | skill:skills/paperclip/references/api-reference.md:paperclip-api-reference:1 | optional_agent_tool | skills/paperclip/references/api-reference.md:1 | | skill:skills/paperclip/references/api-reference.md:response-schemas:9 | optional_agent_tool | skills/paperclip/references/api-reference.md:9 | | skill:skills/paperclip/references/api-reference.md:agent-record-get-api-agents-me-or-get-api-agents-agentid:11 | optional_agent_tool | skills/paperclip/references/api-reference.md:11 | | skill:skills/paperclip/references/api-reference.md:company-portability:44 | optional_agent_tool | skills/paperclip/references/api-reference.md:44 | -| skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:110 | optional_agent_tool | skills/paperclip/references/api-reference.md:110 | -| skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:196 | optional_agent_tool | skills/paperclip/references/api-reference.md:196 | -| skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:238 | control_plane_owned | skills/paperclip/references/api-reference.md:238 | -| skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:277 | control_plane_owned | skills/paperclip/references/api-reference.md:277 | -| skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:321 | optional_agent_tool | skills/paperclip/references/api-reference.md:321 | -| skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:369 | optional_agent_tool | skills/paperclip/references/api-reference.md:369 | -| skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:421 | always_agent_tool | skills/paperclip/references/api-reference.md:421 | -| skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:454 | optional_agent_tool | skills/paperclip/references/api-reference.md:454 | -| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:459 | control_plane_owned | skills/paperclip/references/api-reference.md:459 | -| skill:skills/paperclip/references/api-reference.md:2-check-inbox:463 | control_plane_owned | skills/paperclip/references/api-reference.md:463 | -| skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:470 | optional_agent_tool | skills/paperclip/references/api-reference.md:470 | -| skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:477 | optional_agent_tool | skills/paperclip/references/api-reference.md:477 | -| skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:479 | always_agent_tool | skills/paperclip/references/api-reference.md:479 | -| skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:483 | control_plane_owned | skills/paperclip/references/api-reference.md:483 | -| skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:490 | always_agent_tool | skills/paperclip/references/api-reference.md:490 | -| skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:495 | control_plane_owned | skills/paperclip/references/api-reference.md:495 | -| skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:500 | optional_agent_tool | skills/paperclip/references/api-reference.md:500 | -| skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:504 | control_plane_owned | skills/paperclip/references/api-reference.md:504 | -| skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:518 | always_agent_tool | skills/paperclip/references/api-reference.md:518 | -| skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:523 | control_plane_owned | skills/paperclip/references/api-reference.md:523 | -| skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:528 | optional_agent_tool | skills/paperclip/references/api-reference.md:528 | -| skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:537 | optional_agent_tool | skills/paperclip/references/api-reference.md:537 | -| skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:547 | always_agent_tool | skills/paperclip/references/api-reference.md:547 | -| skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:586 | optional_agent_tool | skills/paperclip/references/api-reference.md:586 | -| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:589 | control_plane_owned | skills/paperclip/references/api-reference.md:589 | -| skill:skills/paperclip/references/api-reference.md:2-check-team-status:593 | optional_agent_tool | skills/paperclip/references/api-reference.md:593 | -| skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:600 | control_plane_owned | skills/paperclip/references/api-reference.md:600 | -| skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:604 | control_plane_owned | skills/paperclip/references/api-reference.md:604 | -| skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:608 | optional_agent_tool | skills/paperclip/references/api-reference.md:608 | -| skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:615 | optional_agent_tool | skills/paperclip/references/api-reference.md:615 | -| skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:621 | control_plane_owned | skills/paperclip/references/api-reference.md:621 | -| skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:626 | optional_agent_tool | skills/paperclip/references/api-reference.md:626 | -| skill:skills/paperclip/references/api-reference.md:comments-and-mentions:632 | always_agent_tool | skills/paperclip/references/api-reference.md:632 | -| skill:skills/paperclip/references/api-reference.md:update:639 | optional_agent_tool | skills/paperclip/references/api-reference.md:639 | -| skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:661 | optional_agent_tool | skills/paperclip/references/api-reference.md:661 | -| skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:665 | optional_agent_tool | skills/paperclip/references/api-reference.md:665 | -| skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:675 | always_agent_tool | skills/paperclip/references/api-reference.md:675 | -| skill:skills/paperclip/references/api-reference.md:company-context:687 | optional_agent_tool | skills/paperclip/references/api-reference.md:687 | -| skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:699 | optional_agent_tool | skills/paperclip/references/api-reference.md:699 | -| skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:719 | optional_agent_tool | skills/paperclip/references/api-reference.md:719 | -| skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:738 | optional_agent_tool | skills/paperclip/references/api-reference.md:738 | -| skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:771 | optional_agent_tool | skills/paperclip/references/api-reference.md:771 | -| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:795 | optional_agent_tool | skills/paperclip/references/api-reference.md:795 | -| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:814 | optional_agent_tool | skills/paperclip/references/api-reference.md:814 | -| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:843 | optional_agent_tool | skills/paperclip/references/api-reference.md:843 | -| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:847 | optional_agent_tool | skills/paperclip/references/api-reference.md:847 | -| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:912 | optional_agent_tool | skills/paperclip/references/api-reference.md:912 | -| skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:921 | always_agent_tool | skills/paperclip/references/api-reference.md:921 | -| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1025 | always_agent_tool | skills/paperclip/references/api-reference.md:1025 | -| skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1083 | always_agent_tool | skills/paperclip/references/api-reference.md:1083 | -| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1089 | always_agent_tool | skills/paperclip/references/api-reference.md:1089 | -| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1204 | optional_agent_tool | skills/paperclip/references/api-reference.md:1204 | -| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1314 | optional_agent_tool | skills/paperclip/references/api-reference.md:1314 | -| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1320 | always_agent_tool | skills/paperclip/references/api-reference.md:1320 | -| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1338 | always_agent_tool | skills/paperclip/references/api-reference.md:1338 | -| skill:skills/paperclip/references/api-reference.md:error-handling:1368 | control_plane_owned | skills/paperclip/references/api-reference.md:1368 | -| skill:skills/paperclip/references/api-reference.md:full-api-reference:1382 | optional_agent_tool | skills/paperclip/references/api-reference.md:1382 | -| skill:skills/paperclip/references/api-reference.md:agents:1384 | optional_agent_tool | skills/paperclip/references/api-reference.md:1384 | -| skill:skills/paperclip/references/api-reference.md:issues-tasks:1405 | optional_agent_tool | skills/paperclip/references/api-reference.md:1405 | -| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1446 | optional_agent_tool | skills/paperclip/references/api-reference.md:1446 | -| skill:skills/paperclip/references/api-reference.md:routines:1470 | optional_agent_tool | skills/paperclip/references/api-reference.md:1470 | -| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1486 | optional_agent_tool | skills/paperclip/references/api-reference.md:1486 | -| skill:skills/paperclip/references/api-reference.md:secrets:1508 | optional_agent_tool | skills/paperclip/references/api-reference.md:1508 | -| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1521 | optional_agent_tool | skills/paperclip/references/api-reference.md:1521 | -| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1621 | optional_agent_tool | skills/paperclip/references/api-reference.md:1621 | -| skill:skills/paperclip/references/api-reference.md:common-mistakes:1661 | optional_agent_tool | skills/paperclip/references/api-reference.md:1661 | +| skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:116 | optional_agent_tool | skills/paperclip/references/api-reference.md:116 | +| skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:202 | optional_agent_tool | skills/paperclip/references/api-reference.md:202 | +| skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:244 | control_plane_owned | skills/paperclip/references/api-reference.md:244 | +| skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:283 | control_plane_owned | skills/paperclip/references/api-reference.md:283 | +| skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:327 | optional_agent_tool | skills/paperclip/references/api-reference.md:327 | +| skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:375 | optional_agent_tool | skills/paperclip/references/api-reference.md:375 | +| skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:427 | always_agent_tool | skills/paperclip/references/api-reference.md:427 | +| skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:460 | optional_agent_tool | skills/paperclip/references/api-reference.md:460 | +| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:465 | control_plane_owned | skills/paperclip/references/api-reference.md:465 | +| skill:skills/paperclip/references/api-reference.md:2-check-inbox:469 | control_plane_owned | skills/paperclip/references/api-reference.md:469 | +| skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:476 | optional_agent_tool | skills/paperclip/references/api-reference.md:476 | +| skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:483 | optional_agent_tool | skills/paperclip/references/api-reference.md:483 | +| skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:485 | always_agent_tool | skills/paperclip/references/api-reference.md:485 | +| skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:489 | control_plane_owned | skills/paperclip/references/api-reference.md:489 | +| skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:496 | always_agent_tool | skills/paperclip/references/api-reference.md:496 | +| skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:501 | control_plane_owned | skills/paperclip/references/api-reference.md:501 | +| skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:506 | optional_agent_tool | skills/paperclip/references/api-reference.md:506 | +| skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:510 | control_plane_owned | skills/paperclip/references/api-reference.md:510 | +| skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:524 | always_agent_tool | skills/paperclip/references/api-reference.md:524 | +| skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:529 | control_plane_owned | skills/paperclip/references/api-reference.md:529 | +| skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:534 | optional_agent_tool | skills/paperclip/references/api-reference.md:534 | +| skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:543 | optional_agent_tool | skills/paperclip/references/api-reference.md:543 | +| skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:553 | always_agent_tool | skills/paperclip/references/api-reference.md:553 | +| skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:592 | optional_agent_tool | skills/paperclip/references/api-reference.md:592 | +| skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:595 | control_plane_owned | skills/paperclip/references/api-reference.md:595 | +| skill:skills/paperclip/references/api-reference.md:2-check-team-status:599 | optional_agent_tool | skills/paperclip/references/api-reference.md:599 | +| skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:606 | control_plane_owned | skills/paperclip/references/api-reference.md:606 | +| skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:610 | control_plane_owned | skills/paperclip/references/api-reference.md:610 | +| skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:614 | optional_agent_tool | skills/paperclip/references/api-reference.md:614 | +| skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:621 | optional_agent_tool | skills/paperclip/references/api-reference.md:621 | +| skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:627 | control_plane_owned | skills/paperclip/references/api-reference.md:627 | +| skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:632 | optional_agent_tool | skills/paperclip/references/api-reference.md:632 | +| skill:skills/paperclip/references/api-reference.md:comments-and-mentions:638 | always_agent_tool | skills/paperclip/references/api-reference.md:638 | +| skill:skills/paperclip/references/api-reference.md:update:645 | optional_agent_tool | skills/paperclip/references/api-reference.md:645 | +| skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:667 | optional_agent_tool | skills/paperclip/references/api-reference.md:667 | +| skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:671 | optional_agent_tool | skills/paperclip/references/api-reference.md:671 | +| skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:681 | always_agent_tool | skills/paperclip/references/api-reference.md:681 | +| skill:skills/paperclip/references/api-reference.md:company-context:693 | optional_agent_tool | skills/paperclip/references/api-reference.md:693 | +| skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:705 | optional_agent_tool | skills/paperclip/references/api-reference.md:705 | +| skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:725 | optional_agent_tool | skills/paperclip/references/api-reference.md:725 | +| skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:744 | optional_agent_tool | skills/paperclip/references/api-reference.md:744 | +| skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:777 | optional_agent_tool | skills/paperclip/references/api-reference.md:777 | +| skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:801 | optional_agent_tool | skills/paperclip/references/api-reference.md:801 | +| skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:820 | optional_agent_tool | skills/paperclip/references/api-reference.md:820 | +| skill:skills/paperclip/references/api-reference.md:governance-and-approvals:849 | optional_agent_tool | skills/paperclip/references/api-reference.md:849 | +| skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:853 | optional_agent_tool | skills/paperclip/references/api-reference.md:853 | +| skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:918 | optional_agent_tool | skills/paperclip/references/api-reference.md:918 | +| skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:927 | always_agent_tool | skills/paperclip/references/api-reference.md:927 | +| skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1031 | always_agent_tool | skills/paperclip/references/api-reference.md:1031 | +| skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1089 | always_agent_tool | skills/paperclip/references/api-reference.md:1089 | +| skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1095 | always_agent_tool | skills/paperclip/references/api-reference.md:1095 | +| skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1210 | optional_agent_tool | skills/paperclip/references/api-reference.md:1210 | +| skill:skills/paperclip/references/api-reference.md:checking-approval-status:1320 | optional_agent_tool | skills/paperclip/references/api-reference.md:1320 | +| skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1326 | always_agent_tool | skills/paperclip/references/api-reference.md:1326 | +| skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1344 | always_agent_tool | skills/paperclip/references/api-reference.md:1344 | +| skill:skills/paperclip/references/api-reference.md:error-handling:1374 | control_plane_owned | skills/paperclip/references/api-reference.md:1374 | +| skill:skills/paperclip/references/api-reference.md:full-api-reference:1388 | optional_agent_tool | skills/paperclip/references/api-reference.md:1388 | +| skill:skills/paperclip/references/api-reference.md:agents:1390 | optional_agent_tool | skills/paperclip/references/api-reference.md:1390 | +| skill:skills/paperclip/references/api-reference.md:issues-tasks:1411 | optional_agent_tool | skills/paperclip/references/api-reference.md:1411 | +| skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1452 | optional_agent_tool | skills/paperclip/references/api-reference.md:1452 | +| skill:skills/paperclip/references/api-reference.md:routines:1476 | optional_agent_tool | skills/paperclip/references/api-reference.md:1476 | +| skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1492 | optional_agent_tool | skills/paperclip/references/api-reference.md:1492 | +| skill:skills/paperclip/references/api-reference.md:secrets:1514 | optional_agent_tool | skills/paperclip/references/api-reference.md:1514 | +| skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1527 | optional_agent_tool | skills/paperclip/references/api-reference.md:1527 | +| skill:skills/paperclip/references/api-reference.md:agent-secret-access:1627 | optional_agent_tool | skills/paperclip/references/api-reference.md:1627 | +| skill:skills/paperclip/references/api-reference.md:common-mistakes:1667 | optional_agent_tool | skills/paperclip/references/api-reference.md:1667 | | skill:skills/paperclip/references/artifacts.md:generated-artifacts-and-work-products:1 | always_agent_tool | skills/paperclip/references/artifacts.md:1 | | skill:skills/paperclip/references/artifacts.md:workspace-only-file-references:15 | optional_agent_tool | skills/paperclip/references/artifacts.md:15 | | skill:skills/paperclip/references/cases.md:cases:1 | optional_agent_tool | skills/paperclip/references/cases.md:1 | diff --git a/packages/paperclip-runner/generated/capability/capabilities.yaml b/packages/paperclip-runner/generated/capability/capabilities.yaml index 4c0ccdb432f..8814ba1da39 100644 --- a/packages/paperclip-runner/generated/capability/capabilities.yaml +++ b/packages/paperclip-runner/generated/capability/capabilities.yaml @@ -56,252 +56,252 @@ "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:198", + "id": "skill:skills/paperclip/SKILL.md:200", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L198:generated-artifacts-and-work-products", + "sourceAnchor": "skills/paperclip/SKILL.md#L200:generated-artifacts-and-work-products", "heading": "Generated Artifacts and Work Products", "primaryDisposition": "always_agent_tool", "semanticOperation": "register_deliverable", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:246", + "id": "skill:skills/paperclip/SKILL.md:248", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L246:status-quick-guide", + "sourceAnchor": "skills/paperclip/SKILL.md#L248:status-quick-guide", "heading": "Status Quick Guide", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:256", + "id": "skill:skills/paperclip/SKILL.md:258", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L256:monitors-and-watchers-say-only-what-you-actually-scheduled", + "sourceAnchor": "skills/paperclip/SKILL.md#L258:monitors-and-watchers-say-only-what-you-actually-scheduled", "heading": "Monitors and Watchers (say only what you actually scheduled)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:269", + "id": "skill:skills/paperclip/SKILL.md:271", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L269:delegating-review-tasks", + "sourceAnchor": "skills/paperclip/SKILL.md#L271:delegating-review-tasks", "heading": "Delegating review tasks", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:280", + "id": "skill:skills/paperclip/SKILL.md:282", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L280:managing-a-user-s-inbox", + "sourceAnchor": "skills/paperclip/SKILL.md#L282:managing-a-user-s-inbox", "heading": "Managing A User's Inbox", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:288", + "id": "skill:skills/paperclip/SKILL.md:290", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L288:issue-dependencies-blockers", + "sourceAnchor": "skills/paperclip/SKILL.md#L290:issue-dependencies-blockers", "heading": "Issue Dependencies (Blockers)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:313", + "id": "skill:skills/paperclip/SKILL.md:315", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L313:requesting-board-approval", + "sourceAnchor": "skills/paperclip/SKILL.md#L315:requesting-board-approval", "heading": "Requesting Board Approval", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:334", + "id": "skill:skills/paperclip/SKILL.md:336", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L334:issue-thread-interactions", + "sourceAnchor": "skills/paperclip/SKILL.md#L336:issue-thread-interactions", "heading": "Issue-Thread Interactions", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:364", + "id": "skill:skills/paperclip/SKILL.md:366", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L364:standalone-decisions", + "sourceAnchor": "skills/paperclip/SKILL.md#L366:standalone-decisions", "heading": "Standalone Decisions", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:468", + "id": "skill:skills/paperclip/SKILL.md:470", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L468:mcp-tool-approval-gates", + "sourceAnchor": "skills/paperclip/SKILL.md#L470:mcp-tool-approval-gates", "heading": "MCP Tool Approval Gates", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:510", + "id": "skill:skills/paperclip/SKILL.md:512", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L510:niche-workflow-pointers", + "sourceAnchor": "skills/paperclip/SKILL.md#L512:niche-workflow-pointers", "heading": "Niche Workflow Pointers", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:520", + "id": "skill:skills/paperclip/SKILL.md:522", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L520:cases", + "sourceAnchor": "skills/paperclip/SKILL.md#L522:cases", "heading": "Cases", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:525", + "id": "skill:skills/paperclip/SKILL.md:527", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L525:company-skills-workflow", + "sourceAnchor": "skills/paperclip/SKILL.md#L527:company-skills-workflow", "heading": "Company Skills Workflow", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:536", + "id": "skill:skills/paperclip/SKILL.md:538", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L536:routines", + "sourceAnchor": "skills/paperclip/SKILL.md#L538:routines", "heading": "Routines", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:547", + "id": "skill:skills/paperclip/SKILL.md:549", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L547:task-files-and-workspace-choices", + "sourceAnchor": "skills/paperclip/SKILL.md#L549:task-files-and-workspace-choices", "heading": "Task Files and Workspace Choices", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:553", + "id": "skill:skills/paperclip/SKILL.md:555", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L553:issue-workspace-runtime-controls", + "sourceAnchor": "skills/paperclip/SKILL.md#L555:issue-workspace-runtime-controls", "heading": "Issue Workspace Runtime Controls", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:560", + "id": "skill:skills/paperclip/SKILL.md:562", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L560:proposing-credentials-safely", + "sourceAnchor": "skills/paperclip/SKILL.md#L562:proposing-credentials-safely", "heading": "Proposing Credentials Safely", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:567", + "id": "skill:skills/paperclip/SKILL.md:569", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L567:reading-granted-secrets", + "sourceAnchor": "skills/paperclip/SKILL.md#L569:reading-granted-secrets", "heading": "Reading Granted Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:593", + "id": "skill:skills/paperclip/SKILL.md:595", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L593:critical-rules", + "sourceAnchor": "skills/paperclip/SKILL.md#L595:critical-rules", "heading": "Critical Rules", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:614", + "id": "skill:skills/paperclip/SKILL.md:616", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L614:comment-style-required", + "sourceAnchor": "skills/paperclip/SKILL.md#L616:comment-style-required", "heading": "Comment Style (Required)", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:646", + "id": "skill:skills/paperclip/SKILL.md:648", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L646:update", + "sourceAnchor": "skills/paperclip/SKILL.md#L648:update", "heading": "Update", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:656", + "id": "skill:skills/paperclip/SKILL.md:658", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L656:task-documents-and-deliverables", + "sourceAnchor": "skills/paperclip/SKILL.md#L658:task-documents-and-deliverables", "heading": "Task Documents and Deliverables", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:670", + "id": "skill:skills/paperclip/SKILL.md:672", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L670:planning-required-when-planning-requested", + "sourceAnchor": "skills/paperclip/SKILL.md#L672:planning-required-when-planning-requested", "heading": "Planning (Required when planning requested)", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:701", + "id": "skill:skills/paperclip/SKILL.md:703", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L701:key-endpoints-hot-routes", + "sourceAnchor": "skills/paperclip/SKILL.md#L703:key-endpoints-hot-routes", "heading": "Key Endpoints (Hot Routes)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:730", + "id": "skill:skills/paperclip/SKILL.md:733", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L730:searching-issues", + "sourceAnchor": "skills/paperclip/SKILL.md#L733:searching-issues", "heading": "Searching Issues", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:740", + "id": "skill:skills/paperclip/SKILL.md:743", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L740:full-reference", + "sourceAnchor": "skills/paperclip/SKILL.md#L743:full-reference", "heading": "Full Reference", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/SKILL.md:744", + "id": "skill:skills/paperclip/SKILL.md:747", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L744:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/SKILL.md#L747:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/SKILL.md:761", + "id": "skill:skills/paperclip/SKILL.md:764", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md#L761:incidental-feedback", + "sourceAnchor": "skills/paperclip/SKILL.md#L764:incidental-feedback", "heading": "Incidental feedback", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", @@ -344,603 +344,603 @@ "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:110", + "id": "skill:skills/paperclip/references/api-reference.md:116", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L110:issue-with-ancestors-get-api-issues-issueid", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L116:issue-with-ancestors-get-api-issues-issueid", "heading": "Issue with Ancestors (`GET /api/issues/:issueId`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:196", + "id": "skill:skills/paperclip/references/api-reference.md:202", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L196:issue-update-response-patch-api-issues-issueid", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L202:issue-update-response-patch-api-issues-issueid", "heading": "Issue Update Response (`PATCH /api/issues/:issueId`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:238", + "id": "skill:skills/paperclip/references/api-reference.md:244", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L238:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L244:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers", "heading": "Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:277", + "id": "skill:skills/paperclip/references/api-reference.md:283", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L277:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L283:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes", "heading": "Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:321", + "id": "skill:skills/paperclip/references/api-reference.md:327", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L321:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L327:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree", "heading": "Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:369", + "id": "skill:skills/paperclip/references/api-reference.md:375", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L369:execution-policy-fields-on-an-issue", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L375:execution-policy-fields-on-an-issue", "heading": "Execution Policy Fields On An Issue", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:421", + "id": "skill:skills/paperclip/references/api-reference.md:427", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L421:cross-agent-review-gates", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L427:cross-agent-review-gates", "heading": "Cross-Agent Review Gates", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:454", + "id": "skill:skills/paperclip/references/api-reference.md:460", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L454:worked-example-ic-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L460:worked-example-ic-heartbeat", "heading": "Worked Example: IC Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:459", + "id": "skill:skills/paperclip/references/api-reference.md:465", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L459:1-identity-skip-if-already-in-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L465:1-identity-skip-if-already-in-context", "heading": "1. Identity (skip if already in context)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:463", + "id": "skill:skills/paperclip/references/api-reference.md:469", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L463:2-check-inbox", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L469:2-check-inbox", "heading": "2. Check inbox", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:470", + "id": "skill:skills/paperclip/references/api-reference.md:476", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L470:3-already-have-issue-101-in-progress-highest-priority-continue-it", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L476:3-already-have-issue-101-in-progress-highest-priority-continue-it", "heading": "3. Already have issue-101 in_progress (highest priority). Continue it.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:477", + "id": "skill:skills/paperclip/references/api-reference.md:483", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L477:4-do-the-actual-work-write-code-run-tests", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L483:4-do-the-actual-work-write-code-run-tests", "heading": "4. Do the actual work (write code, run tests)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:479", + "id": "skill:skills/paperclip/references/api-reference.md:485", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L479:5-work-is-done-update-status-and-comment-in-one-call", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L485:5-work-is-done-update-status-and-comment-in-one-call", "heading": "5. Work is done. Update status and comment in one call.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:483", + "id": "skill:skills/paperclip/references/api-reference.md:489", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L483:6-still-have-time-checkout-the-next-task", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L489:6-still-have-time-checkout-the-next-task", "heading": "6. Still have time. Checkout the next task.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:490", + "id": "skill:skills/paperclip/references/api-reference.md:496", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L490:7-made-partial-progress-not-done-yet-comment-and-exit", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L496:7-made-partial-progress-not-done-yet-comment-and-exit", "heading": "7. Made partial progress, not done yet. Comment and exit.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:495", + "id": "skill:skills/paperclip/references/api-reference.md:501", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L495:worked-example-report-a-board-user-s-mine-inbox", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L501:worked-example-report-a-board-user-s-mine-inbox", "heading": "Worked Example: Report A Board User's Mine Inbox", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:500", + "id": "skill:skills/paperclip/references/api-reference.md:506", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L500:board-user-created-the-requesting-issue", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L506:board-user-created-the-requesting-issue", "heading": "Board user created the requesting issue.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:504", + "id": "skill:skills/paperclip/references/api-reference.md:510", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L504:fetch-the-board-user-s-mine-inbox-issues", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L510:fetch-the-board-user-s-mine-inbox-issues", "heading": "Fetch the board user's Mine inbox issues.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:518", + "id": "skill:skills/paperclip/references/api-reference.md:524", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L518:summarize-it-back-to-the-board-in-a-comment-or-document", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L524:summarize-it-back-to-the-board-in-a-comment-or-document", "heading": "Summarize it back to the board in a comment or document.", "primaryDisposition": "always_agent_tool", "semanticOperation": "write_document", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:523", + "id": "skill:skills/paperclip/references/api-reference.md:529", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L523:worked-example-archive-a-resolved-inbox-item", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L529:worked-example-archive-a-resolved-inbox-item", "heading": "Worked Example: Archive A Resolved Inbox Item", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:528", + "id": "skill:skills/paperclip/references/api-reference.md:534", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L528:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L534:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run", "heading": "The responsible user's id is resolved from the authenticated agent run.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:537", + "id": "skill:skills/paperclip/references/api-reference.md:543", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L537:reverse-the-archive-if-it-was-premature-or-no-longer-desired", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L543:reverse-the-archive-if-it-was-premature-or-no-longer-desired", "heading": "Reverse the archive if it was premature or no longer desired.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:547", + "id": "skill:skills/paperclip/references/api-reference.md:553", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L547:worked-example-reviewer-approver-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L553:worked-example-reviewer-approver-heartbeat", "heading": "Worked Example: Reviewer / Approver Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:586", + "id": "skill:skills/paperclip/references/api-reference.md:592", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L586:worked-example-manager-heartbeat", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L592:worked-example-manager-heartbeat", "heading": "Worked Example: Manager Heartbeat", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:589", + "id": "skill:skills/paperclip/references/api-reference.md:595", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L589:1-identity-skip-if-already-in-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L595:1-identity-skip-if-already-in-context", "heading": "1. Identity (skip if already in context)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:593", + "id": "skill:skills/paperclip/references/api-reference.md:599", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L593:2-check-team-status", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L599:2-check-team-status", "heading": "2. Check team status", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:600", + "id": "skill:skills/paperclip/references/api-reference.md:606", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L600:3-agent-42-is-blocked-read-comments", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L606:3-agent-42-is-blocked-read-comments", "heading": "3. Agent-42 is blocked. Read comments.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:604", + "id": "skill:skills/paperclip/references/api-reference.md:610", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L604:4-unblock-reassign-and-comment", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L610:4-unblock-reassign-and-comment", "heading": "4. Unblock: reassign and comment.", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:608", + "id": "skill:skills/paperclip/references/api-reference.md:614", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L608:5-check-own-assignments", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L614:5-check-own-assignments", "heading": "5. Check own assignments.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, - { - "id": "skill:skills/paperclip/references/api-reference.md:615", - "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L615:6-create-subtasks-and-delegate", - "heading": "6. Create subtasks and delegate.", - "primaryDisposition": "optional_agent_tool", - "semanticOperation": "scoped_discovery", - "expectedMockState": "operation_result" - }, { "id": "skill:skills/paperclip/references/api-reference.md:621", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L621:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L621:6-create-subtasks-and-delegate", + "heading": "6. Create subtasks and delegate.", + "primaryDisposition": "optional_agent_tool", + "semanticOperation": "scoped_discovery", + "expectedMockState": "operation_result" + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:627", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L627:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves", "heading": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:626", + "id": "skill:skills/paperclip/references/api-reference.md:632", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L626:7-dashboard-for-health-check", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L632:7-dashboard-for-health-check", "heading": "7. Dashboard for health check.", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:632", + "id": "skill:skills/paperclip/references/api-reference.md:638", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L632:comments-and-mentions", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L638:comments-and-mentions", "heading": "Comments and @-mentions", "primaryDisposition": "always_agent_tool", "semanticOperation": "report_progress", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:639", + "id": "skill:skills/paperclip/references/api-reference.md:645", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L639:update", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L645:update", "heading": "Update", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:661", + "id": "skill:skills/paperclip/references/api-reference.md:667", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L661:cross-team-work-and-delegation", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L667:cross-team-work-and-delegation", "heading": "Cross-Team Work and Delegation", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:665", + "id": "skill:skills/paperclip/references/api-reference.md:671", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L665:receiving-cross-team-work", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L671:receiving-cross-team-work", "heading": "Receiving cross-team work", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:675", + "id": "skill:skills/paperclip/references/api-reference.md:681", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L675:questions-and-dependencies", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L681:questions-and-dependencies", "heading": "Questions and dependencies", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:687", + "id": "skill:skills/paperclip/references/api-reference.md:693", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L687:company-context", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L693:company-context", "heading": "Company Context", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:699", + "id": "skill:skills/paperclip/references/api-reference.md:705", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L699:company-branding-ceo-board", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L705:company-branding-ceo-board", "heading": "Company Branding (CEO / Board)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:719", + "id": "skill:skills/paperclip/references/api-reference.md:725", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L719:openclaw-invite-prompt-ceo", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L725:openclaw-invite-prompt-ceo", "heading": "OpenClaw Invite Prompt (CEO)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:738", + "id": "skill:skills/paperclip/references/api-reference.md:744", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L738:setting-agent-instructions-path", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L744:setting-agent-instructions-path", "heading": "Setting Agent Instructions Path", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:771", + "id": "skill:skills/paperclip/references/api-reference.md:777", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L771:project-setup-create-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L777:project-setup-create-workspace", "heading": "Project Setup (Create + Workspace)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:795", + "id": "skill:skills/paperclip/references/api-reference.md:801", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L795:option-a-one-call-create-with-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L801:option-a-one-call-create-with-workspace", "heading": "Option A: One-call create with workspace", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:814", + "id": "skill:skills/paperclip/references/api-reference.md:820", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L814:option-b-two-calls-project-first-then-workspace", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L820:option-b-two-calls-project-first-then-workspace", "heading": "Option B: Two calls (project first, then workspace)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:843", + "id": "skill:skills/paperclip/references/api-reference.md:849", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L843:governance-and-approvals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L849:governance-and-approvals", "heading": "Governance and Approvals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:847", + "id": "skill:skills/paperclip/references/api-reference.md:853", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L847:requesting-a-hire-management-only", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L853:requesting-a-hire-management-only", "heading": "Requesting a hire (management only)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:912", + "id": "skill:skills/paperclip/references/api-reference.md:918", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L912:ceo-strategy-approval", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L918:ceo-strategy-approval", "heading": "CEO strategy approval", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:921", + "id": "skill:skills/paperclip/references/api-reference.md:927", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L921:questions-and-waiting-for-human-input", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L927:questions-and-waiting-for-human-input", "heading": "Questions and waiting for human input", "primaryDisposition": "always_agent_tool", "semanticOperation": "request_human_input", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1025", + "id": "skill:skills/paperclip/references/api-reference.md:1031", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1025:issue-thread-confirmations", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1031:issue-thread-confirmations", "heading": "Issue-thread confirmations", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1083", + "id": "skill:skills/paperclip/references/api-reference.md:1089", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1083:conversational-confirmation-answers", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1089:conversational-confirmation-answers", "heading": "Conversational confirmation answers", "primaryDisposition": "always_agent_tool", "semanticOperation": "call_api", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1089", + "id": "skill:skills/paperclip/references/api-reference.md:1095", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1089:checkbox-confirmations", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1095:checkbox-confirmations", "heading": "Checkbox confirmations", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1204", + "id": "skill:skills/paperclip/references/api-reference.md:1210", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1204:item-verdict-requests", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1210:item-verdict-requests", "heading": "Item verdict requests", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, - { - "id": "skill:skills/paperclip/references/api-reference.md:1314", - "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1314:checking-approval-status", - "heading": "Checking approval status", - "primaryDisposition": "optional_agent_tool", - "semanticOperation": "scoped_discovery", - "expectedMockState": "operation_result" - }, { "id": "skill:skills/paperclip/references/api-reference.md:1320", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1320:approval-follow-up-requesting-agent", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1320:checking-approval-status", + "heading": "Checking approval status", + "primaryDisposition": "optional_agent_tool", + "semanticOperation": "scoped_discovery", + "expectedMockState": "operation_result" + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:1326", + "kind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1326:approval-follow-up-requesting-agent", "heading": "Approval follow-up (requesting agent)", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1338", + "id": "skill:skills/paperclip/references/api-reference.md:1344", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1338:issue-lifecycle", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1344:issue-lifecycle", "heading": "Issue Lifecycle", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1368", + "id": "skill:skills/paperclip/references/api-reference.md:1374", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1368:error-handling", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1374:error-handling", "heading": "Error Handling", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1382", + "id": "skill:skills/paperclip/references/api-reference.md:1388", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1382:full-api-reference", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1388:full-api-reference", "heading": "Full API Reference", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1384", + "id": "skill:skills/paperclip/references/api-reference.md:1390", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1384:agents", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1390:agents", "heading": "Agents", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1405", + "id": "skill:skills/paperclip/references/api-reference.md:1411", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1405:issues-tasks", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1411:issues-tasks", "heading": "Issues (Tasks)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1446", + "id": "skill:skills/paperclip/references/api-reference.md:1452", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1446:companies-projects-goals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1452:companies-projects-goals", "heading": "Companies, Projects, Goals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1470", + "id": "skill:skills/paperclip/references/api-reference.md:1476", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1470:routines", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1476:routines", "heading": "Routines", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1486", + "id": "skill:skills/paperclip/references/api-reference.md:1492", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1486:approvals-costs-activity-dashboard", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1492:approvals-costs-activity-dashboard", "heading": "Approvals, Costs, Activity, Dashboard", "primaryDisposition": "control_plane_owned", "semanticOperation": "runtime_reconciliation", "expectedMockState": "runtime_decision_record" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1508", + "id": "skill:skills/paperclip/references/api-reference.md:1514", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1508:secrets", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1514:secrets", "heading": "Secrets", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1521", + "id": "skill:skills/paperclip/references/api-reference.md:1527", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1521:agent-secret-proposals", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1527:agent-secret-proposals", "heading": "Agent secret proposals", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1574", + "id": "skill:skills/paperclip/references/api-reference.md:1580", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1574:re-bind-an-existing-secret-under-a-new-path-no-secret-id", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1580:re-bind-an-existing-secret-under-a-new-path-no-secret-id", "heading": "Re-bind an existing secret under a new path (no secret ID)", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1621", + "id": "skill:skills/paperclip/references/api-reference.md:1627", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1621:agent-secret-access", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1627:agent-secret-access", "heading": "Agent secret access", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", "expectedMockState": "operation_result" }, { - "id": "skill:skills/paperclip/references/api-reference.md:1661", + "id": "skill:skills/paperclip/references/api-reference.md:1667", "kind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md#L1661:common-mistakes", + "sourceAnchor": "skills/paperclip/references/api-reference.md#L1667:common-mistakes", "heading": "Common Mistakes", "primaryDisposition": "optional_agent_tool", "semanticOperation": "scoped_discovery", diff --git a/packages/paperclip-runner/generated/capability/capability-contract.md b/packages/paperclip-runner/generated/capability/capability-contract.md index 6c6665b696a..d8493676bb2 100644 --- a/packages/paperclip-runner/generated/capability/capability-contract.md +++ b/packages/paperclip-runner/generated/capability/capability-contract.md @@ -5,6 +5,6 @@ Generated by `scripts/generate-capability-contract.mjs`; do not edit generated f - Skill/reference headings: 165 - Legacy MCP tools: 42 - Eval cases: 106 across 16 groups -- Deterministic content SHA-256: `83447a4f9463f9a3d73dd4bea78b580df0356545ba00d0da96763282653bdf0f` +- Deterministic content SHA-256: `74c5fcb9ac01e20178834438536f40edc8b630e15571b447cd783a77b8b69c7b` Every row has exactly one primary disposition, a source anchor, a semantic operation, and a mock-state expectation. diff --git a/packages/paperclip-runner/spec/capability/capabilities.yaml b/packages/paperclip-runner/spec/capability/capabilities.yaml index 7899ff444d0..a3bc4ddadac 100644 --- a/packages/paperclip-runner/spec/capability/capabilities.yaml +++ b/packages/paperclip-runner/spec/capability/capabilities.yaml @@ -105,9 +105,9 @@ ] }, { - "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:198", + "id": "skill:skills/paperclip/SKILL.md:generated-artifacts-and-work-products:200", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:198", + "sourceAnchor": "skills/paperclip/SKILL.md:200", "title": "Generated Artifacts and Work Products", "expectedSemantics": "Skill guidance headed “Generated Artifacts and Work Products”.", "primaryDisposition": "always_agent_tool", @@ -116,13 +116,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:198" + "skill:skills/paperclip/SKILL.md:200" ] }, { - "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:246", + "id": "skill:skills/paperclip/SKILL.md:status-quick-guide:248", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:246", + "sourceAnchor": "skills/paperclip/SKILL.md:248", "title": "Status Quick Guide", "expectedSemantics": "Skill guidance headed “Status Quick Guide”.", "primaryDisposition": "control_plane_owned", @@ -131,13 +131,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:246" + "skill:skills/paperclip/SKILL.md:248" ] }, { - "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:256", + "id": "skill:skills/paperclip/SKILL.md:monitors-and-watchers-say-only-what-you-actually-scheduled:258", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:256", + "sourceAnchor": "skills/paperclip/SKILL.md:258", "title": "Monitors and Watchers (say only what you actually scheduled)", "expectedSemantics": "Skill guidance headed “Monitors and Watchers (say only what you actually scheduled)”.", "primaryDisposition": "optional_agent_tool", @@ -146,13 +146,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:256" + "skill:skills/paperclip/SKILL.md:258" ] }, { - "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:269", + "id": "skill:skills/paperclip/SKILL.md:delegating-review-tasks:271", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:269", + "sourceAnchor": "skills/paperclip/SKILL.md:271", "title": "Delegating review tasks", "expectedSemantics": "Skill guidance headed “Delegating review tasks”.", "primaryDisposition": "always_agent_tool", @@ -161,13 +161,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:269" + "skill:skills/paperclip/SKILL.md:271" ] }, { - "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:280", + "id": "skill:skills/paperclip/SKILL.md:managing-a-user-s-inbox:282", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:280", + "sourceAnchor": "skills/paperclip/SKILL.md:282", "title": "Managing A User's Inbox", "expectedSemantics": "Skill guidance headed “Managing A User's Inbox”.", "primaryDisposition": "control_plane_owned", @@ -176,13 +176,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:280" + "skill:skills/paperclip/SKILL.md:282" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:288", + "id": "skill:skills/paperclip/SKILL.md:issue-dependencies-blockers:290", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:288", + "sourceAnchor": "skills/paperclip/SKILL.md:290", "title": "Issue Dependencies (Blockers)", "expectedSemantics": "Skill guidance headed “Issue Dependencies (Blockers)”.", "primaryDisposition": "control_plane_owned", @@ -191,13 +191,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:288" + "skill:skills/paperclip/SKILL.md:290" ] }, { - "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:313", + "id": "skill:skills/paperclip/SKILL.md:requesting-board-approval:315", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:313", + "sourceAnchor": "skills/paperclip/SKILL.md:315", "title": "Requesting Board Approval", "expectedSemantics": "Skill guidance headed “Requesting Board Approval”.", "primaryDisposition": "optional_agent_tool", @@ -206,13 +206,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:313" + "skill:skills/paperclip/SKILL.md:315" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:334", + "id": "skill:skills/paperclip/SKILL.md:issue-thread-interactions:336", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:334", + "sourceAnchor": "skills/paperclip/SKILL.md:336", "title": "Issue-Thread Interactions", "expectedSemantics": "Skill guidance headed “Issue-Thread Interactions”.", "primaryDisposition": "optional_agent_tool", @@ -221,13 +221,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:334" + "skill:skills/paperclip/SKILL.md:336" ] }, { - "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:364", + "id": "skill:skills/paperclip/SKILL.md:standalone-decisions:366", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:364", + "sourceAnchor": "skills/paperclip/SKILL.md:366", "title": "Standalone Decisions", "expectedSemantics": "Skill guidance headed “Standalone Decisions”.", "primaryDisposition": "optional_agent_tool", @@ -236,13 +236,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:364" + "skill:skills/paperclip/SKILL.md:366" ] }, { - "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:468", + "id": "skill:skills/paperclip/SKILL.md:mcp-tool-approval-gates:470", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:468", + "sourceAnchor": "skills/paperclip/SKILL.md:470", "title": "MCP Tool Approval Gates", "expectedSemantics": "Skill guidance headed “MCP Tool Approval Gates”.", "primaryDisposition": "optional_agent_tool", @@ -251,13 +251,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:468" + "skill:skills/paperclip/SKILL.md:470" ] }, { - "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:510", + "id": "skill:skills/paperclip/SKILL.md:niche-workflow-pointers:512", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:510", + "sourceAnchor": "skills/paperclip/SKILL.md:512", "title": "Niche Workflow Pointers", "expectedSemantics": "Skill guidance headed “Niche Workflow Pointers”.", "primaryDisposition": "optional_agent_tool", @@ -266,13 +266,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:510" + "skill:skills/paperclip/SKILL.md:512" ] }, { - "id": "skill:skills/paperclip/SKILL.md:cases:520", + "id": "skill:skills/paperclip/SKILL.md:cases:522", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:520", + "sourceAnchor": "skills/paperclip/SKILL.md:522", "title": "Cases", "expectedSemantics": "Skill guidance headed “Cases”.", "primaryDisposition": "optional_agent_tool", @@ -281,13 +281,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:520" + "skill:skills/paperclip/SKILL.md:522" ] }, { - "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:525", + "id": "skill:skills/paperclip/SKILL.md:company-skills-workflow:527", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:525", + "sourceAnchor": "skills/paperclip/SKILL.md:527", "title": "Company Skills Workflow", "expectedSemantics": "Skill guidance headed “Company Skills Workflow”.", "primaryDisposition": "optional_agent_tool", @@ -296,13 +296,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:525" + "skill:skills/paperclip/SKILL.md:527" ] }, { - "id": "skill:skills/paperclip/SKILL.md:routines:536", + "id": "skill:skills/paperclip/SKILL.md:routines:538", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:536", + "sourceAnchor": "skills/paperclip/SKILL.md:538", "title": "Routines", "expectedSemantics": "Skill guidance headed “Routines”.", "primaryDisposition": "optional_agent_tool", @@ -311,13 +311,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:536" + "skill:skills/paperclip/SKILL.md:538" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:547", + "id": "skill:skills/paperclip/SKILL.md:task-files-and-workspace-choices:549", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:547", + "sourceAnchor": "skills/paperclip/SKILL.md:549", "title": "Task Files and Workspace Choices", "expectedSemantics": "Skill guidance headed “Task Files and Workspace Choices”.", "primaryDisposition": "optional_agent_tool", @@ -326,13 +326,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:547" + "skill:skills/paperclip/SKILL.md:549" ] }, { - "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:553", + "id": "skill:skills/paperclip/SKILL.md:issue-workspace-runtime-controls:555", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:553", + "sourceAnchor": "skills/paperclip/SKILL.md:555", "title": "Issue Workspace Runtime Controls", "expectedSemantics": "Skill guidance headed “Issue Workspace Runtime Controls”.", "primaryDisposition": "optional_agent_tool", @@ -341,13 +341,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:553" + "skill:skills/paperclip/SKILL.md:555" ] }, { - "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:560", + "id": "skill:skills/paperclip/SKILL.md:proposing-credentials-safely:562", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:560", + "sourceAnchor": "skills/paperclip/SKILL.md:562", "title": "Proposing Credentials Safely", "expectedSemantics": "Skill guidance headed “Proposing Credentials Safely”.", "primaryDisposition": "optional_agent_tool", @@ -356,13 +356,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:560" + "skill:skills/paperclip/SKILL.md:562" ] }, { - "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:567", + "id": "skill:skills/paperclip/SKILL.md:reading-granted-secrets:569", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:567", + "sourceAnchor": "skills/paperclip/SKILL.md:569", "title": "Reading Granted Secrets", "expectedSemantics": "Skill guidance headed “Reading Granted Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -371,13 +371,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:567" + "skill:skills/paperclip/SKILL.md:569" ] }, { - "id": "skill:skills/paperclip/SKILL.md:critical-rules:593", + "id": "skill:skills/paperclip/SKILL.md:critical-rules:595", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:593", + "sourceAnchor": "skills/paperclip/SKILL.md:595", "title": "Critical Rules", "expectedSemantics": "Skill guidance headed “Critical Rules”.", "primaryDisposition": "optional_agent_tool", @@ -386,13 +386,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:593" + "skill:skills/paperclip/SKILL.md:595" ] }, { - "id": "skill:skills/paperclip/SKILL.md:comment-style-required:614", + "id": "skill:skills/paperclip/SKILL.md:comment-style-required:616", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:614", + "sourceAnchor": "skills/paperclip/SKILL.md:616", "title": "Comment Style (Required)", "expectedSemantics": "Skill guidance headed “Comment Style (Required)”.", "primaryDisposition": "always_agent_tool", @@ -401,13 +401,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:614" + "skill:skills/paperclip/SKILL.md:616" ] }, { - "id": "skill:skills/paperclip/SKILL.md:update:646", + "id": "skill:skills/paperclip/SKILL.md:update:648", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:646", + "sourceAnchor": "skills/paperclip/SKILL.md:648", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -416,13 +416,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:646" + "skill:skills/paperclip/SKILL.md:648" ] }, { - "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:656", + "id": "skill:skills/paperclip/SKILL.md:task-documents-and-deliverables:658", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:656", + "sourceAnchor": "skills/paperclip/SKILL.md:658", "title": "Task Documents and Deliverables", "expectedSemantics": "Skill guidance headed “Task Documents and Deliverables”.", "primaryDisposition": "always_agent_tool", @@ -431,13 +431,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:656" + "skill:skills/paperclip/SKILL.md:658" ] }, { - "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:670", + "id": "skill:skills/paperclip/SKILL.md:planning-required-when-planning-requested:672", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:670", + "sourceAnchor": "skills/paperclip/SKILL.md:672", "title": "Planning (Required when planning requested)", "expectedSemantics": "Skill guidance headed “Planning (Required when planning requested)”.", "primaryDisposition": "optional_agent_tool", @@ -446,13 +446,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:670" + "skill:skills/paperclip/SKILL.md:672" ] }, { - "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:701", + "id": "skill:skills/paperclip/SKILL.md:key-endpoints-hot-routes:703", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:701", + "sourceAnchor": "skills/paperclip/SKILL.md:703", "title": "Key Endpoints (Hot Routes)", "expectedSemantics": "Skill guidance headed “Key Endpoints (Hot Routes)”.", "primaryDisposition": "optional_agent_tool", @@ -461,13 +461,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:701" + "skill:skills/paperclip/SKILL.md:703" ] }, { - "id": "skill:skills/paperclip/SKILL.md:searching-issues:730", + "id": "skill:skills/paperclip/SKILL.md:searching-issues:733", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:730", + "sourceAnchor": "skills/paperclip/SKILL.md:733", "title": "Searching Issues", "expectedSemantics": "Skill guidance headed “Searching Issues”.", "primaryDisposition": "optional_agent_tool", @@ -476,13 +476,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:730" + "skill:skills/paperclip/SKILL.md:733" ] }, { - "id": "skill:skills/paperclip/SKILL.md:full-reference:740", + "id": "skill:skills/paperclip/SKILL.md:full-reference:743", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:740", + "sourceAnchor": "skills/paperclip/SKILL.md:743", "title": "Full Reference", "expectedSemantics": "Skill guidance headed “Full Reference”.", "primaryDisposition": "optional_agent_tool", @@ -491,13 +491,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:740" + "skill:skills/paperclip/SKILL.md:743" ] }, { - "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:744", + "id": "skill:skills/paperclip/SKILL.md:conversational-confirmation-answers:747", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:744", + "sourceAnchor": "skills/paperclip/SKILL.md:747", "title": "Conversational confirmation answers", "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", @@ -506,13 +506,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:744" + "skill:skills/paperclip/SKILL.md:747" ] }, { - "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:761", + "id": "skill:skills/paperclip/SKILL.md:incidental-feedback:764", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/SKILL.md:761", + "sourceAnchor": "skills/paperclip/SKILL.md:764", "title": "Incidental feedback", "expectedSemantics": "Skill guidance headed “Incidental feedback”.", "primaryDisposition": "optional_agent_tool", @@ -521,7 +521,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/SKILL.md:761" + "skill:skills/paperclip/SKILL.md:764" ] }, { @@ -585,9 +585,9 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:110", + "id": "skill:skills/paperclip/references/api-reference.md:issue-with-ancestors-get-api-issues-issueid:116", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:110", + "sourceAnchor": "skills/paperclip/references/api-reference.md:116", "title": "Issue with Ancestors (`GET /api/issues/:issueId`)", "expectedSemantics": "Skill guidance headed “Issue with Ancestors (`GET /api/issues/:issueId`)”.", "primaryDisposition": "optional_agent_tool", @@ -596,13 +596,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:110" + "skill:skills/paperclip/references/api-reference.md:116" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:196", + "id": "skill:skills/paperclip/references/api-reference.md:issue-update-response-patch-api-issues-issueid:202", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:196", + "sourceAnchor": "skills/paperclip/references/api-reference.md:202", "title": "Issue Update Response (`PATCH /api/issues/:issueId`)", "expectedSemantics": "Skill guidance headed “Issue Update Response (`PATCH /api/issues/:issueId`)”.", "primaryDisposition": "optional_agent_tool", @@ -611,13 +611,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:196" + "skill:skills/paperclip/references/api-reference.md:202" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:238", + "id": "skill:skills/paperclip/references/api-reference.md:blocker-diagnostics-get-api-issues-issueid-diagnostics-blockers:244", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:238", + "sourceAnchor": "skills/paperclip/references/api-reference.md:244", "title": "Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)", "expectedSemantics": "Skill guidance headed “Blocker Diagnostics (`GET /api/issues/:issueId/diagnostics/blockers`)”.", "primaryDisposition": "control_plane_owned", @@ -626,13 +626,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:238" + "skill:skills/paperclip/references/api-reference.md:244" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:277", + "id": "skill:skills/paperclip/references/api-reference.md:wake-diagnostics-get-api-issues-issueid-diagnostics-wakes:283", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:277", + "sourceAnchor": "skills/paperclip/references/api-reference.md:283", "title": "Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)", "expectedSemantics": "Skill guidance headed “Wake Diagnostics (`GET /api/issues/:issueId/diagnostics/wakes`)”.", "primaryDisposition": "control_plane_owned", @@ -641,13 +641,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:277" + "skill:skills/paperclip/references/api-reference.md:283" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:321", + "id": "skill:skills/paperclip/references/api-reference.md:subtree-diagnostics-get-api-issues-issueid-diagnostics-subtree:327", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:321", + "sourceAnchor": "skills/paperclip/references/api-reference.md:327", "title": "Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)", "expectedSemantics": "Skill guidance headed “Subtree Diagnostics (`GET /api/issues/:issueId/diagnostics/subtree`)”.", "primaryDisposition": "optional_agent_tool", @@ -656,13 +656,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:321" + "skill:skills/paperclip/references/api-reference.md:327" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:369", + "id": "skill:skills/paperclip/references/api-reference.md:execution-policy-fields-on-an-issue:375", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:369", + "sourceAnchor": "skills/paperclip/references/api-reference.md:375", "title": "Execution Policy Fields On An Issue", "expectedSemantics": "Skill guidance headed “Execution Policy Fields On An Issue”.", "primaryDisposition": "optional_agent_tool", @@ -671,13 +671,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:369" + "skill:skills/paperclip/references/api-reference.md:375" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:421", + "id": "skill:skills/paperclip/references/api-reference.md:cross-agent-review-gates:427", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:421", + "sourceAnchor": "skills/paperclip/references/api-reference.md:427", "title": "Cross-Agent Review Gates", "expectedSemantics": "Skill guidance headed “Cross-Agent Review Gates”.", "primaryDisposition": "always_agent_tool", @@ -686,13 +686,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:421" + "skill:skills/paperclip/references/api-reference.md:427" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:454", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-ic-heartbeat:460", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:454", + "sourceAnchor": "skills/paperclip/references/api-reference.md:460", "title": "Worked Example: IC Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: IC Heartbeat”.", "primaryDisposition": "optional_agent_tool", @@ -701,13 +701,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:454" + "skill:skills/paperclip/references/api-reference.md:460" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:459", + "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:465", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:459", + "sourceAnchor": "skills/paperclip/references/api-reference.md:465", "title": "1. Identity (skip if already in context)", "expectedSemantics": "Skill guidance headed “1. Identity (skip if already in context)”.", "primaryDisposition": "control_plane_owned", @@ -716,13 +716,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:459" + "skill:skills/paperclip/references/api-reference.md:465" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:2-check-inbox:463", + "id": "skill:skills/paperclip/references/api-reference.md:2-check-inbox:469", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:463", + "sourceAnchor": "skills/paperclip/references/api-reference.md:469", "title": "2. Check inbox", "expectedSemantics": "Skill guidance headed “2. Check inbox”.", "primaryDisposition": "control_plane_owned", @@ -731,13 +731,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:463" + "skill:skills/paperclip/references/api-reference.md:469" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:470", + "id": "skill:skills/paperclip/references/api-reference.md:3-already-have-issue-101-inprogress-highest-priority-continue-it:476", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:470", + "sourceAnchor": "skills/paperclip/references/api-reference.md:476", "title": "3. Already have issue-101 in_progress (highest priority). Continue it.", "expectedSemantics": "Skill guidance headed “3. Already have issue-101 in_progress (highest priority). Continue it.”.", "primaryDisposition": "optional_agent_tool", @@ -746,13 +746,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:470" + "skill:skills/paperclip/references/api-reference.md:476" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:477", + "id": "skill:skills/paperclip/references/api-reference.md:4-do-the-actual-work-write-code-run-tests:483", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:477", + "sourceAnchor": "skills/paperclip/references/api-reference.md:483", "title": "4. Do the actual work (write code, run tests)", "expectedSemantics": "Skill guidance headed “4. Do the actual work (write code, run tests)”.", "primaryDisposition": "optional_agent_tool", @@ -760,44 +760,44 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:477" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:479", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:479", - "title": "5. Work is done. Update status and comment in one call.", - "expectedSemantics": "Skill guidance headed “5. Work is done. Update status and comment in one call.”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:479" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:483", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:483", - "title": "6. Still have time. Checkout the next task.", - "expectedSemantics": "Skill guidance headed “6. Still have time. Checkout the next task.”.", - "primaryDisposition": "control_plane_owned", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:483" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:490", + "id": "skill:skills/paperclip/references/api-reference.md:5-work-is-done-update-status-and-comment-in-one-call:485", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:490", + "sourceAnchor": "skills/paperclip/references/api-reference.md:485", + "title": "5. Work is done. Update status and comment in one call.", + "expectedSemantics": "Skill guidance headed “5. Work is done. Update status and comment in one call.”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:485" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:6-still-have-time-checkout-the-next-task:489", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:489", + "title": "6. Still have time. Checkout the next task.", + "expectedSemantics": "Skill guidance headed “6. Still have time. Checkout the next task.”.", + "primaryDisposition": "control_plane_owned", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:489" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:7-made-partial-progress-not-done-yet-comment-and-exit:496", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:496", "title": "7. Made partial progress, not done yet. Comment and exit.", "expectedSemantics": "Skill guidance headed “7. Made partial progress, not done yet. Comment and exit.”.", "primaryDisposition": "always_agent_tool", @@ -806,13 +806,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:490" + "skill:skills/paperclip/references/api-reference.md:496" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:495", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-report-a-board-user-s-mine-inbox:501", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:495", + "sourceAnchor": "skills/paperclip/references/api-reference.md:501", "title": "Worked Example: Report A Board User's Mine Inbox", "expectedSemantics": "Skill guidance headed “Worked Example: Report A Board User's Mine Inbox”.", "primaryDisposition": "control_plane_owned", @@ -821,13 +821,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:495" + "skill:skills/paperclip/references/api-reference.md:501" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:500", + "id": "skill:skills/paperclip/references/api-reference.md:board-user-created-the-requesting-issue:506", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:500", + "sourceAnchor": "skills/paperclip/references/api-reference.md:506", "title": "Board user created the requesting issue.", "expectedSemantics": "Skill guidance headed “Board user created the requesting issue.”.", "primaryDisposition": "optional_agent_tool", @@ -836,13 +836,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:500" + "skill:skills/paperclip/references/api-reference.md:506" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:504", + "id": "skill:skills/paperclip/references/api-reference.md:fetch-the-board-user-s-mine-inbox-issues:510", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:504", + "sourceAnchor": "skills/paperclip/references/api-reference.md:510", "title": "Fetch the board user's Mine inbox issues.", "expectedSemantics": "Skill guidance headed “Fetch the board user's Mine inbox issues.”.", "primaryDisposition": "control_plane_owned", @@ -851,13 +851,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:504" + "skill:skills/paperclip/references/api-reference.md:510" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:518", + "id": "skill:skills/paperclip/references/api-reference.md:summarize-it-back-to-the-board-in-a-comment-or-document:524", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:518", + "sourceAnchor": "skills/paperclip/references/api-reference.md:524", "title": "Summarize it back to the board in a comment or document.", "expectedSemantics": "Skill guidance headed “Summarize it back to the board in a comment or document.”.", "primaryDisposition": "always_agent_tool", @@ -866,13 +866,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:518" + "skill:skills/paperclip/references/api-reference.md:524" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:523", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-archive-a-resolved-inbox-item:529", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:523", + "sourceAnchor": "skills/paperclip/references/api-reference.md:529", "title": "Worked Example: Archive A Resolved Inbox Item", "expectedSemantics": "Skill guidance headed “Worked Example: Archive A Resolved Inbox Item”.", "primaryDisposition": "control_plane_owned", @@ -881,13 +881,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:523" + "skill:skills/paperclip/references/api-reference.md:529" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:528", + "id": "skill:skills/paperclip/references/api-reference.md:the-responsible-user-s-id-is-resolved-from-the-authenticated-agent-run:534", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:528", + "sourceAnchor": "skills/paperclip/references/api-reference.md:534", "title": "The responsible user's id is resolved from the authenticated agent run.", "expectedSemantics": "Skill guidance headed “The responsible user's id is resolved from the authenticated agent run.”.", "primaryDisposition": "optional_agent_tool", @@ -896,13 +896,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:528" + "skill:skills/paperclip/references/api-reference.md:534" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:537", + "id": "skill:skills/paperclip/references/api-reference.md:reverse-the-archive-if-it-was-premature-or-no-longer-desired:543", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:537", + "sourceAnchor": "skills/paperclip/references/api-reference.md:543", "title": "Reverse the archive if it was premature or no longer desired.", "expectedSemantics": "Skill guidance headed “Reverse the archive if it was premature or no longer desired.”.", "primaryDisposition": "optional_agent_tool", @@ -911,13 +911,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:537" + "skill:skills/paperclip/references/api-reference.md:543" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:547", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-reviewer-approver-heartbeat:553", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:547", + "sourceAnchor": "skills/paperclip/references/api-reference.md:553", "title": "Worked Example: Reviewer / Approver Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: Reviewer / Approver Heartbeat”.", "primaryDisposition": "always_agent_tool", @@ -926,13 +926,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:547" + "skill:skills/paperclip/references/api-reference.md:553" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:586", + "id": "skill:skills/paperclip/references/api-reference.md:worked-example-manager-heartbeat:592", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:586", + "sourceAnchor": "skills/paperclip/references/api-reference.md:592", "title": "Worked Example: Manager Heartbeat", "expectedSemantics": "Skill guidance headed “Worked Example: Manager Heartbeat”.", "primaryDisposition": "optional_agent_tool", @@ -941,13 +941,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:586" + "skill:skills/paperclip/references/api-reference.md:592" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:589", + "id": "skill:skills/paperclip/references/api-reference.md:1-identity-skip-if-already-in-context:595", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:589", + "sourceAnchor": "skills/paperclip/references/api-reference.md:595", "title": "1. Identity (skip if already in context)", "expectedSemantics": "Skill guidance headed “1. Identity (skip if already in context)”.", "primaryDisposition": "control_plane_owned", @@ -956,13 +956,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:589" + "skill:skills/paperclip/references/api-reference.md:595" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:2-check-team-status:593", + "id": "skill:skills/paperclip/references/api-reference.md:2-check-team-status:599", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:593", + "sourceAnchor": "skills/paperclip/references/api-reference.md:599", "title": "2. Check team status", "expectedSemantics": "Skill guidance headed “2. Check team status”.", "primaryDisposition": "optional_agent_tool", @@ -971,13 +971,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:593" + "skill:skills/paperclip/references/api-reference.md:599" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:600", + "id": "skill:skills/paperclip/references/api-reference.md:3-agent-42-is-blocked-read-comments:606", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:600", + "sourceAnchor": "skills/paperclip/references/api-reference.md:606", "title": "3. Agent-42 is blocked. Read comments.", "expectedSemantics": "Skill guidance headed “3. Agent-42 is blocked. Read comments.”.", "primaryDisposition": "control_plane_owned", @@ -986,13 +986,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:600" + "skill:skills/paperclip/references/api-reference.md:606" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:604", + "id": "skill:skills/paperclip/references/api-reference.md:4-unblock-reassign-and-comment:610", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:604", + "sourceAnchor": "skills/paperclip/references/api-reference.md:610", "title": "4. Unblock: reassign and comment.", "expectedSemantics": "Skill guidance headed “4. Unblock: reassign and comment.”.", "primaryDisposition": "control_plane_owned", @@ -1001,13 +1001,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:604" + "skill:skills/paperclip/references/api-reference.md:610" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:608", + "id": "skill:skills/paperclip/references/api-reference.md:5-check-own-assignments:614", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:608", + "sourceAnchor": "skills/paperclip/references/api-reference.md:614", "title": "5. Check own assignments.", "expectedSemantics": "Skill guidance headed “5. Check own assignments.”.", "primaryDisposition": "optional_agent_tool", @@ -1016,13 +1016,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:608" + "skill:skills/paperclip/references/api-reference.md:614" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:615", + "id": "skill:skills/paperclip/references/api-reference.md:6-create-subtasks-and-delegate:621", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:615", + "sourceAnchor": "skills/paperclip/references/api-reference.md:621", "title": "6. Create subtasks and delegate.", "expectedSemantics": "Skill guidance headed “6. Create subtasks and delegate.”.", "primaryDisposition": "optional_agent_tool", @@ -1030,47 +1030,32 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:615" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:621", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:621", - "title": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", - "expectedSemantics": "Skill guidance headed “^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.”.", - "primaryDisposition": "control_plane_owned", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:621" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:626", + "id": "skill:skills/paperclip/references/api-reference.md:load-tests-depend-on-caching-layer-being-done-first-paperclip-will-auto-wake-agent-55-when-the-blocker-resolves:627", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:626", - "title": "7. Dashboard for health check.", - "expectedSemantics": "Skill guidance headed “7. Dashboard for health check.”.", - "primaryDisposition": "optional_agent_tool", + "sourceAnchor": "skills/paperclip/references/api-reference.md:627", + "title": "^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.", + "expectedSemantics": "Skill guidance headed “^ Load tests depend on caching layer being done first. Paperclip will auto-wake agent-55 when the blocker resolves.”.", + "primaryDisposition": "control_plane_owned", "requiredGrants": [], "assertionClasses": [ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:626" + "skill:skills/paperclip/references/api-reference.md:627" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:comments-and-mentions:632", + "id": "skill:skills/paperclip/references/api-reference.md:7-dashboard-for-health-check:632", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/references/api-reference.md:632", - "title": "Comments and @-mentions", - "expectedSemantics": "Skill guidance headed “Comments and @-mentions”.", - "primaryDisposition": "always_agent_tool", + "title": "7. Dashboard for health check.", + "expectedSemantics": "Skill guidance headed “7. Dashboard for health check.”.", + "primaryDisposition": "optional_agent_tool", "requiredGrants": [], "assertionClasses": [ "control_plane_invariant" @@ -1080,9 +1065,24 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:update:639", + "id": "skill:skills/paperclip/references/api-reference.md:comments-and-mentions:638", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:639", + "sourceAnchor": "skills/paperclip/references/api-reference.md:638", + "title": "Comments and @-mentions", + "expectedSemantics": "Skill guidance headed “Comments and @-mentions”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:638" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:update:645", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:645", "title": "Update", "expectedSemantics": "Skill guidance headed “Update”.", "primaryDisposition": "optional_agent_tool", @@ -1091,13 +1091,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:639" + "skill:skills/paperclip/references/api-reference.md:645" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:661", + "id": "skill:skills/paperclip/references/api-reference.md:cross-team-work-and-delegation:667", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:661", + "sourceAnchor": "skills/paperclip/references/api-reference.md:667", "title": "Cross-Team Work and Delegation", "expectedSemantics": "Skill guidance headed “Cross-Team Work and Delegation”.", "primaryDisposition": "optional_agent_tool", @@ -1106,13 +1106,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:661" + "skill:skills/paperclip/references/api-reference.md:667" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:665", + "id": "skill:skills/paperclip/references/api-reference.md:receiving-cross-team-work:671", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:665", + "sourceAnchor": "skills/paperclip/references/api-reference.md:671", "title": "Receiving cross-team work", "expectedSemantics": "Skill guidance headed “Receiving cross-team work”.", "primaryDisposition": "optional_agent_tool", @@ -1121,13 +1121,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:665" + "skill:skills/paperclip/references/api-reference.md:671" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:675", + "id": "skill:skills/paperclip/references/api-reference.md:questions-and-dependencies:681", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:675", + "sourceAnchor": "skills/paperclip/references/api-reference.md:681", "title": "Questions and dependencies", "expectedSemantics": "Skill guidance headed “Questions and dependencies”.", "primaryDisposition": "always_agent_tool", @@ -1136,13 +1136,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:675" + "skill:skills/paperclip/references/api-reference.md:681" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:company-context:687", + "id": "skill:skills/paperclip/references/api-reference.md:company-context:693", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:687", + "sourceAnchor": "skills/paperclip/references/api-reference.md:693", "title": "Company Context", "expectedSemantics": "Skill guidance headed “Company Context”.", "primaryDisposition": "optional_agent_tool", @@ -1151,13 +1151,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:687" + "skill:skills/paperclip/references/api-reference.md:693" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:699", + "id": "skill:skills/paperclip/references/api-reference.md:company-branding-ceo-board:705", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:699", + "sourceAnchor": "skills/paperclip/references/api-reference.md:705", "title": "Company Branding (CEO / Board)", "expectedSemantics": "Skill guidance headed “Company Branding (CEO / Board)”.", "primaryDisposition": "optional_agent_tool", @@ -1166,13 +1166,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:699" + "skill:skills/paperclip/references/api-reference.md:705" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:719", + "id": "skill:skills/paperclip/references/api-reference.md:openclaw-invite-prompt-ceo:725", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:719", + "sourceAnchor": "skills/paperclip/references/api-reference.md:725", "title": "OpenClaw Invite Prompt (CEO)", "expectedSemantics": "Skill guidance headed “OpenClaw Invite Prompt (CEO)”.", "primaryDisposition": "optional_agent_tool", @@ -1181,13 +1181,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:719" + "skill:skills/paperclip/references/api-reference.md:725" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:738", + "id": "skill:skills/paperclip/references/api-reference.md:setting-agent-instructions-path:744", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:738", + "sourceAnchor": "skills/paperclip/references/api-reference.md:744", "title": "Setting Agent Instructions Path", "expectedSemantics": "Skill guidance headed “Setting Agent Instructions Path”.", "primaryDisposition": "optional_agent_tool", @@ -1196,13 +1196,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:738" + "skill:skills/paperclip/references/api-reference.md:744" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:771", + "id": "skill:skills/paperclip/references/api-reference.md:project-setup-create-workspace:777", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:771", + "sourceAnchor": "skills/paperclip/references/api-reference.md:777", "title": "Project Setup (Create + Workspace)", "expectedSemantics": "Skill guidance headed “Project Setup (Create + Workspace)”.", "primaryDisposition": "optional_agent_tool", @@ -1211,13 +1211,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:771" + "skill:skills/paperclip/references/api-reference.md:777" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:795", + "id": "skill:skills/paperclip/references/api-reference.md:option-a-one-call-create-with-workspace:801", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:795", + "sourceAnchor": "skills/paperclip/references/api-reference.md:801", "title": "Option A: One-call create with workspace", "expectedSemantics": "Skill guidance headed “Option A: One-call create with workspace”.", "primaryDisposition": "optional_agent_tool", @@ -1226,13 +1226,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:795" + "skill:skills/paperclip/references/api-reference.md:801" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:814", + "id": "skill:skills/paperclip/references/api-reference.md:option-b-two-calls-project-first-then-workspace:820", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:814", + "sourceAnchor": "skills/paperclip/references/api-reference.md:820", "title": "Option B: Two calls (project first, then workspace)", "expectedSemantics": "Skill guidance headed “Option B: Two calls (project first, then workspace)”.", "primaryDisposition": "optional_agent_tool", @@ -1241,13 +1241,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:814" + "skill:skills/paperclip/references/api-reference.md:820" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:843", + "id": "skill:skills/paperclip/references/api-reference.md:governance-and-approvals:849", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:843", + "sourceAnchor": "skills/paperclip/references/api-reference.md:849", "title": "Governance and Approvals", "expectedSemantics": "Skill guidance headed “Governance and Approvals”.", "primaryDisposition": "optional_agent_tool", @@ -1256,13 +1256,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:843" + "skill:skills/paperclip/references/api-reference.md:849" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:847", + "id": "skill:skills/paperclip/references/api-reference.md:requesting-a-hire-management-only:853", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:847", + "sourceAnchor": "skills/paperclip/references/api-reference.md:853", "title": "Requesting a hire (management only)", "expectedSemantics": "Skill guidance headed “Requesting a hire (management only)”.", "primaryDisposition": "optional_agent_tool", @@ -1271,13 +1271,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:847" + "skill:skills/paperclip/references/api-reference.md:853" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:912", + "id": "skill:skills/paperclip/references/api-reference.md:ceo-strategy-approval:918", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:912", + "sourceAnchor": "skills/paperclip/references/api-reference.md:918", "title": "CEO strategy approval", "expectedSemantics": "Skill guidance headed “CEO strategy approval”.", "primaryDisposition": "optional_agent_tool", @@ -1286,13 +1286,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:912" + "skill:skills/paperclip/references/api-reference.md:918" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:921", + "id": "skill:skills/paperclip/references/api-reference.md:questions-and-waiting-for-human-input:927", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:921", + "sourceAnchor": "skills/paperclip/references/api-reference.md:927", "title": "Questions and waiting for human input", "expectedSemantics": "Skill guidance headed “Questions and waiting for human input”.", "primaryDisposition": "always_agent_tool", @@ -1301,13 +1301,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:921" + "skill:skills/paperclip/references/api-reference.md:927" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1025", + "id": "skill:skills/paperclip/references/api-reference.md:issue-thread-confirmations:1031", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1025", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1031", "title": "Issue-thread confirmations", "expectedSemantics": "Skill guidance headed “Issue-thread confirmations”.", "primaryDisposition": "always_agent_tool", @@ -1316,30 +1316,15 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1025" + "skill:skills/paperclip/references/api-reference.md:1031" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1083", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1083", - "title": "Conversational confirmation answers", - "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1083" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1089", + "id": "skill:skills/paperclip/references/api-reference.md:conversational-confirmation-answers:1089", "sourceKind": "skill_heading", "sourceAnchor": "skills/paperclip/references/api-reference.md:1089", - "title": "Checkbox confirmations", - "expectedSemantics": "Skill guidance headed “Checkbox confirmations”.", + "title": "Conversational confirmation answers", + "expectedSemantics": "Skill guidance headed “Conversational confirmation answers”.", "primaryDisposition": "always_agent_tool", "requiredGrants": [], "assertionClasses": [ @@ -1350,9 +1335,24 @@ ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1204", + "id": "skill:skills/paperclip/references/api-reference.md:checkbox-confirmations:1095", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1204", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1095", + "title": "Checkbox confirmations", + "expectedSemantics": "Skill guidance headed “Checkbox confirmations”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:1095" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:item-verdict-requests:1210", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1210", "title": "Item verdict requests", "expectedSemantics": "Skill guidance headed “Item verdict requests”.", "primaryDisposition": "optional_agent_tool", @@ -1361,13 +1361,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1204" + "skill:skills/paperclip/references/api-reference.md:1210" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1314", + "id": "skill:skills/paperclip/references/api-reference.md:checking-approval-status:1320", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1314", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1320", "title": "Checking approval status", "expectedSemantics": "Skill guidance headed “Checking approval status”.", "primaryDisposition": "optional_agent_tool", @@ -1375,29 +1375,29 @@ "assertionClasses": [ "control_plane_invariant" ], - "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1314" - ] - }, - { - "id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1320", - "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1320", - "title": "Approval follow-up (requesting agent)", - "expectedSemantics": "Skill guidance headed “Approval follow-up (requesting agent)”.", - "primaryDisposition": "always_agent_tool", - "requiredGrants": [], - "assertionClasses": [ - "control_plane_invariant" - ], "evidenceIds": [ "skill:skills/paperclip/references/api-reference.md:1320" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1338", + "id": "skill:skills/paperclip/references/api-reference.md:approval-follow-up-requesting-agent:1326", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1338", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1326", + "title": "Approval follow-up (requesting agent)", + "expectedSemantics": "Skill guidance headed “Approval follow-up (requesting agent)”.", + "primaryDisposition": "always_agent_tool", + "requiredGrants": [], + "assertionClasses": [ + "control_plane_invariant" + ], + "evidenceIds": [ + "skill:skills/paperclip/references/api-reference.md:1326" + ] + }, + { + "id": "skill:skills/paperclip/references/api-reference.md:issue-lifecycle:1344", + "sourceKind": "skill_heading", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1344", "title": "Issue Lifecycle", "expectedSemantics": "Skill guidance headed “Issue Lifecycle”.", "primaryDisposition": "always_agent_tool", @@ -1406,13 +1406,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1338" + "skill:skills/paperclip/references/api-reference.md:1344" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:error-handling:1368", + "id": "skill:skills/paperclip/references/api-reference.md:error-handling:1374", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1368", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1374", "title": "Error Handling", "expectedSemantics": "Skill guidance headed “Error Handling”.", "primaryDisposition": "control_plane_owned", @@ -1421,13 +1421,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1368" + "skill:skills/paperclip/references/api-reference.md:1374" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1382", + "id": "skill:skills/paperclip/references/api-reference.md:full-api-reference:1388", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1382", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1388", "title": "Full API Reference", "expectedSemantics": "Skill guidance headed “Full API Reference”.", "primaryDisposition": "optional_agent_tool", @@ -1436,13 +1436,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1382" + "skill:skills/paperclip/references/api-reference.md:1388" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agents:1384", + "id": "skill:skills/paperclip/references/api-reference.md:agents:1390", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1384", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1390", "title": "Agents", "expectedSemantics": "Skill guidance headed “Agents”.", "primaryDisposition": "optional_agent_tool", @@ -1451,13 +1451,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1384" + "skill:skills/paperclip/references/api-reference.md:1390" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1405", + "id": "skill:skills/paperclip/references/api-reference.md:issues-tasks:1411", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1405", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1411", "title": "Issues (Tasks)", "expectedSemantics": "Skill guidance headed “Issues (Tasks)”.", "primaryDisposition": "optional_agent_tool", @@ -1466,13 +1466,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1405" + "skill:skills/paperclip/references/api-reference.md:1411" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1446", + "id": "skill:skills/paperclip/references/api-reference.md:companies-projects-goals:1452", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1446", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1452", "title": "Companies, Projects, Goals", "expectedSemantics": "Skill guidance headed “Companies, Projects, Goals”.", "primaryDisposition": "optional_agent_tool", @@ -1481,13 +1481,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1446" + "skill:skills/paperclip/references/api-reference.md:1452" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:routines:1470", + "id": "skill:skills/paperclip/references/api-reference.md:routines:1476", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1470", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1476", "title": "Routines", "expectedSemantics": "Skill guidance headed “Routines”.", "primaryDisposition": "optional_agent_tool", @@ -1496,13 +1496,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1470" + "skill:skills/paperclip/references/api-reference.md:1476" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1486", + "id": "skill:skills/paperclip/references/api-reference.md:approvals-costs-activity-dashboard:1492", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1486", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1492", "title": "Approvals, Costs, Activity, Dashboard", "expectedSemantics": "Skill guidance headed “Approvals, Costs, Activity, Dashboard”.", "primaryDisposition": "optional_agent_tool", @@ -1511,13 +1511,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1486" + "skill:skills/paperclip/references/api-reference.md:1492" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:secrets:1508", + "id": "skill:skills/paperclip/references/api-reference.md:secrets:1514", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1508", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1514", "title": "Secrets", "expectedSemantics": "Skill guidance headed “Secrets”.", "primaryDisposition": "optional_agent_tool", @@ -1526,13 +1526,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1508" + "skill:skills/paperclip/references/api-reference.md:1514" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1521", + "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-proposals:1527", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1521", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1527", "title": "Agent secret proposals", "expectedSemantics": "Skill guidance headed “Agent secret proposals”.", "primaryDisposition": "optional_agent_tool", @@ -1541,13 +1541,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1521" + "skill:skills/paperclip/references/api-reference.md:1527" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1621", + "id": "skill:skills/paperclip/references/api-reference.md:agent-secret-access:1627", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1621", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1627", "title": "Agent secret access", "expectedSemantics": "Skill guidance headed “Agent secret access”.", "primaryDisposition": "optional_agent_tool", @@ -1556,13 +1556,13 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1621" + "skill:skills/paperclip/references/api-reference.md:1627" ] }, { - "id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1661", + "id": "skill:skills/paperclip/references/api-reference.md:common-mistakes:1667", "sourceKind": "skill_heading", - "sourceAnchor": "skills/paperclip/references/api-reference.md:1661", + "sourceAnchor": "skills/paperclip/references/api-reference.md:1667", "title": "Common Mistakes", "expectedSemantics": "Skill guidance headed “Common Mistakes”.", "primaryDisposition": "optional_agent_tool", @@ -1571,7 +1571,7 @@ "control_plane_invariant" ], "evidenceIds": [ - "skill:skills/paperclip/references/api-reference.md:1661" + "skill:skills/paperclip/references/api-reference.md:1667" ] }, { diff --git a/server/src/__tests__/activity-routes.test.ts b/server/src/__tests__/activity-routes.test.ts index 76671447903..d2090f9e5a1 100644 --- a/server/src/__tests__/activity-routes.test.ts +++ b/server/src/__tests__/activity-routes.test.ts @@ -126,6 +126,33 @@ describe("activity routes", () => { mockAccessService.canUser.mockResolvedValue(false); }); + it("lets an agent read activity for an authorized issue", async () => { + mockIssueService.getByIdentifier.mockResolvedValue({ + id: "issue-1", companyId: "company-1", projectId: null, parentId: null, + assigneeAgentId: "agent-1", assigneeUserId: null, status: "done", + }); + mockActivityService.forIssue.mockResolvedValue([{ action: "issue.recovery_action_resolved", actorType: "system" }]); + const app = await createApp({ type: "agent", companyId: "company-1", agentId: "agent-1" }); + const res = await requestApp(app, baseUrl => request(baseUrl).get("/api/issues/TEST-1/activity")); + expect(res.status).toBe(200); + expect(mockAccessService.decide).toHaveBeenCalledWith(expect.objectContaining({ + action: "issue:read", resource: expect.objectContaining({ companyId: "company-1", issueId: "issue-1" }), + })); + expect(mockActivityService.forIssue).toHaveBeenCalledWith("issue-1"); + expect(res.body).toEqual([{ action: "issue.recovery_action_resolved", actorType: "system" }]); + }); + + it.each(["boundary", "company"])("denies agent activity reads outside the %s scope", async (kind) => { + const issue = { id: "issue-1", companyId: kind === "company" ? "company-2" : "company-1" }; + mockIssueService.getById.mockResolvedValue(issue); + mockIssueService.getByIdentifier.mockResolvedValue(issue); + mockAccessService.decide.mockResolvedValue({ allowed: false }); + const app = await createApp({ type: "agent", companyId: "company-1", agentId: "agent-1" }); + const res = await requestApp(app, baseUrl => request(baseUrl).get("/api/issues/issue-1/activity")); + expect(res.status).toBe(kind === "company" ? 404 : 403); + expect(mockActivityService.forIssue).not.toHaveBeenCalled(); + }); + it("returns redacted all-actors rows to a basic company reader", async () => { mockAgentActionAuditService.list.mockResolvedValue({ items: [{ diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts index f9ce2234e52..50b97e82246 100644 --- a/server/src/__tests__/heartbeat-process-recovery.test.ts +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -2415,7 +2415,7 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { .toMatchObject({ status: "active", resolvedAt: null }); }); - it("keeps existing terminal-task disposition for a native finalization hold", async () => { + it("retains the exact native owner hold without changing a terminal task's disposition", async () => { const f = await seedPendingNativeFinalization(); await db.update(heartbeatRuns).set({ runnerProfileJson: { nativeWorkspaceFinalizationOwner: { unknown: true } } }) .where(eq(heartbeatRuns.id, f.runId)); @@ -2423,6 +2423,13 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => { await heartbeat.recoverNativeRunsAfterRestart(); const [hold] = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, f.issueId)); await db.update(issues).set({ status: "done" }).where(eq(issues.id, f.issueId)); + for (let attempt = 0; attempt < 3; attempt += 1) { + await heartbeat.reconcileStrandedAssignedIssues(); + expect((await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.id, hold.id)))[0]) + .toEqual(hold); + expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0].status).toBe("done"); + } + await db.update(heartbeatRuns).set({ runnerProfileJson: {} }).where(eq(heartbeatRuns.id, f.runId)); await heartbeat.reconcileStrandedAssignedIssues(); expect((await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.id, hold.id)))[0]) .toMatchObject({ status: "resolved", resolutionNote: "source_terminal" }); diff --git a/server/src/__tests__/issue-recovery-actions.test.ts b/server/src/__tests__/issue-recovery-actions.test.ts index dd814866b58..05e96216f61 100644 --- a/server/src/__tests__/issue-recovery-actions.test.ts +++ b/server/src/__tests__/issue-recovery-actions.test.ts @@ -38,6 +38,8 @@ import { deliverReconciledExecutions } from "../services/execution-recovery-reso import { workspaceOperationService, isNativeWorkspaceFinalizationOperationActive } from "../services/workspace-operations.js"; import { issueRecoveryActionService } from "../services/issue-recovery-actions.js"; import { recoveryService } from "../services/recovery/service.js"; +import { issueService } from "../services/issues.js"; +import { withNativeWorkspaceFinalizationOwnership } from "../services/native-runtime/native-workspace-finalization-ownership.js"; import { noticeMetadataReferencesRecoveryAction } from "../services/recovery/successful-run-handoff.js"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); @@ -2479,6 +2481,51 @@ describeEmbeddedPostgres("issue recovery actions", () => { trigger: "read_projection", recoveryActionId: action.id, }); + expect(activityRows.find((row) => row.action === "issue.recovery_action_resolved")).toMatchObject({ + actorType: "system", actorId: "recovery", agentId: null, + }); + }); + + it.each(["done", "cancelled"])("keeps one workspace owner hold across reads and sweeps of a %s task", async (status) => { + const { companyId, coderId, sourceIssueId } = await seedCompany(); + const runId = randomUUID(); + const owner = { token: randomUUID(), hostname: "old-controller.invalid", pid: 42, processStartedAt: new Date(0).toISOString() }; + await db.insert(heartbeatRuns).values({ + id: runId, companyId, agentId: coderId, nativeIssueId: sourceIssueId, + runtimeMode: "native", status: "failed", finishedAt: new Date(), + runnerProfileJson: { nativeWorkspaceFinalizationOwner: owner }, + }); + await db.update(issues).set({ status }).where(eq(issues.id, sourceIssueId)); + const userId = "board-reader"; + const app = createApp({ type: "board", source: "local_implicit", userId }); + const copyback = vi.fn(); + const recovery = recoveryService(db, { enqueueWakeup: vi.fn(async () => null) }); + let firstAction: typeof issueRecoveryActions.$inferSelect | undefined; + for (let tick = 0; tick < 3; tick += 1) { + expect(await withNativeWorkspaceFinalizationOwnership({ db, companyId, runId }, copyback)).toEqual({ acquired: false }); + const detail = await request(app).get(`/api/issues/${sourceIssueId}`).expect(200); + expect(detail.body.activeRecoveryAction).toMatchObject({ + cause: "native_workspace_finalization_owner_unverified", status: "active", attemptCount: 1, + }); + await request(app).get(`/api/companies/${companyId}/issues?view=compact`).expect(200); + await request(app).get(`/api/issues/${sourceIssueId}/recovery-actions`).expect(200); + await recovery.reconcileStrandedAssignedIssues(); + const actions = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, sourceIssueId)); + expect(actions).toHaveLength(1); + firstAction ??= actions[0]!; + expect(actions[0]).toEqual(firstAction); + } + expect(copyback).not.toHaveBeenCalled(); + expect(await issueService(db).list(companyId, { touchedByUserId: userId })).toEqual([]); + expect(await db.select().from(activityLog).where(eq(activityLog.entityId, sourceIssueId))).toHaveLength(0); + + // Only clearing the exact physical owner receipt makes this hold stale. + await db.update(heartbeatRuns).set({ runnerProfileJson: {} }).where(eq(heartbeatRuns.id, runId)); + const cleared = await request(app).get(`/api/issues/${sourceIssueId}`).expect(200); + expect(cleared.body.activeRecoveryAction).toBeNull(); + const rows = await db.select().from(activityLog).where(eq(activityLog.entityId, sourceIssueId)); + expect(rows).toEqual([expect.objectContaining({ actorType: "system", actorId: "recovery", action: "issue.recovery_action_resolved" })]); + expect(await issueService(db).list(companyId, { touchedByUserId: userId })).toEqual([]); }); it("keeps active recovery visible when a plain comment does not create a live path", async () => { diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 535990d7fd1..4475e075122 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -668,6 +668,39 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { ]); }); + it("ignores historical reader-attributed recovery cleanup without hiding explicit recovery decisions", async () => { + const companyId = await seedAssignableAgentCompany(); + const userId = "board-user"; + const old = new Date("2026-03-26T10:00:00Z"); + const recent = new Date("2026-03-26T15:00:00Z"); + const variants = [ + { id: randomUUID(), details: { source: "source_revalidation", trigger: "read_projection" }, counts: false }, + { id: randomUUID(), details: null, counts: true }, + { id: randomUUID(), details: { outcome: "restored" }, counts: true }, + { id: randomUUID(), details: { source: "source_revalidation", trigger: "issue_update" }, counts: true }, + ]; + await db.insert(issues).values(variants.map(({ id }) => ({ + id, companyId, title: "Recovery audit", status: "done", updatedAt: old, + }))); + await db.insert(activityLog).values(variants.map(({ id, details }) => ({ + companyId, actorType: "user", actorId: userId, action: "issue.recovery_action_resolved", + entityType: "issue", entityId: id, details, createdAt: recent, + }))); + await db.insert(issueComments).values(variants.map(({ id }) => ({ + companyId, issueId: id, authorUserId: "other-user", body: "An older update", createdAt: old, + }))); + + const expectedIds = variants.filter(v => v.counts).map(v => v.id).sort(); + expect((await svc.list(companyId, { touchedByUserId: userId })).map(v => v.id).sort()).toEqual(expectedIds); + expect(await svc.countUnreadTouchedByUser(companyId, userId)).toBe(expectedIds.length); + const rows = await svc.list(companyId, {}); + for (const variant of variants) { + expect(rows.find(v => v.id === variant.id)?.lastActivityAt).toEqual(variant.counts ? recent : old); + } + // Audit evidence is preserved, including the wrongly attributed old row. + expect(await db.select().from(activityLog).where(eq(activityLog.companyId, companyId))).toHaveLength(variants.length); + }); + function agentRow(companyId: string, input: { id: string; name: string; diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 13c5e848f6a..3ecbbdab36f 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -1,6 +1,7 @@ import { parseObject } from "../adapters/utils.js"; import { enqueueFastResponse } from "../services/fast-responses.js"; import { hasRequiredWorkspaceRecovery, LEGACY_WORKSPACE_RECOVERY_SCHEMA } from "../services/workspace-restore-recovery-state.js"; +import { hasUnverifiedNativeWorkspaceFinalizationOwner } from "../services/native-runtime/native-workspace-finalization-recovery-state.js"; import { monitorPoliciesEqual, applyActorMonitorScheduledBy, assertCanManageIssueMonitor, summarizeIssueMonitor } from "../services/issue-monitors.js"; import type { IssuePrivacyConstraints } from "@paperclipai/shared"; import { canActorReadHeartbeatRun } from "../services/heartbeat-run-privacy.js"; @@ -4344,6 +4345,10 @@ export function issueRoutes( : input.activeRecoveryAction; if (!activeRecoveryAction) return null; + if (await hasUnverifiedNativeWorkspaceFinalizationOwner(db, activeRecoveryAction)) { + return activeRecoveryAction; + } + const resolutionNote = await classifySourceRecoveryRevalidation(input); if (!resolutionNote) return activeRecoveryAction; @@ -4357,13 +4362,12 @@ export function issueRoutes( }); if (!resolved) return activeRecoveryAction; - const actor = input.actor; await logActivity(db, { companyId: input.issue.companyId, - actorType: actor?.actorType ?? "system", - actorId: actor?.actorId ?? "system", - agentId: actor?.agentId ?? null, - runId: actor?.runId ?? null, + actorType: "system", + actorId: "recovery", + agentId: null, + runId: input.actor?.runId ?? null, action: "issue.recovery_action_resolved", entityType: "issue", entityId: input.issue.id, diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 35a606cf7d5..e34121a99fa 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -2842,6 +2842,16 @@ const ISSUE_USER_PARTICIPATION_ACTIVITY_ACTIONS = [ "issue.work_product_updated", ] as const; +// Old read projections attributed this automatic cleanup to the reader. Keep +// the audit rows, but do not count them as participation or issue activity. +function automaticReadRecoveryCleanupCondition() { + return sql`( + ${activityLog.action} = 'issue.recovery_action_resolved' + AND COALESCE(${activityLog.details}->>'source', '') = 'source_revalidation' + AND COALESCE(${activityLog.details}->>'trigger', '') = 'read_projection' + )`; +} + function touchedByUserCondition(companyId: string, userId: string) { return sql` ( @@ -2855,6 +2865,7 @@ function touchedByUserCondition(companyId: string, userId: string) { AND ${activityLog.companyId} = ${companyId} AND ${activityLog.actorType} = 'user' AND ${activityLog.actorId} = ${userId} + AND NOT ${automaticReadRecoveryCleanupCondition()} AND ${activityLog.action} IN (${sql.join( ISSUE_USER_PARTICIPATION_ACTIVITY_ACTIONS.map( (action) => sql`${action}`, @@ -2960,6 +2971,7 @@ function issueLatestLogAtExpr(companyId: string) { WHERE ${activityLog.companyId} = ${companyId} AND ${activityLog.entityType} = 'issue' AND ${activityLog.entityId} = ${issues.id}::text + AND NOT ${automaticReadRecoveryCleanupCondition()} AND ${activityLog.action} NOT IN (${sql.join( ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql`${action}`), sql`, `, @@ -5138,6 +5150,7 @@ async function lastActivityStatsForIssues( eq(activityLog.companyId, companyId), eq(activityLog.entityType, "issue"), inArray(activityLog.entityId, issueIdChunk), + sql`NOT ${automaticReadRecoveryCleanupCondition()}`, sql`${activityLog.action} NOT IN (${sql.join( ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map( (action) => sql`${action}`, diff --git a/server/src/services/native-runtime/native-workspace-finalization-ownership.ts b/server/src/services/native-runtime/native-workspace-finalization-ownership.ts index 84227de0049..fcbff05fdee 100644 --- a/server/src/services/native-runtime/native-workspace-finalization-ownership.ts +++ b/server/src/services/native-runtime/native-workspace-finalization-ownership.ts @@ -1,5 +1,6 @@ import { issueRecoveryActionService } from "../issue-recovery-actions.js"; import { nativeSha256 } from "./canonical.js"; +import { NATIVE_WORKSPACE_OWNER_UNVERIFIED } from "./native-workspace-finalization-recovery-state.js"; import { randomUUID } from "node:crypto"; import os from "node:os"; import { and, eq, sql } from "drizzle-orm"; @@ -74,11 +75,18 @@ export async function withNativeWorkspaceFinalizationOwnership( const prior = readOwner(rawPrior); if (rawPrior != null) { const requireStopVerification = async () => { - if (run.issueId) await issueRecoveryActionService(input.db).upsertSourceScoped({ + const fingerprint = nativeSha256({ runId: input.runId, owner: rawPrior }); + const recovery = issueRecoveryActionService(input.db); + const existing = run.issueId ? await recovery.getActiveForIssue(input.companyId, run.issueId) : null; + // Rechecking the same physical owner is not a new recovery attempt. + if (existing?.cause === NATIVE_WORKSPACE_OWNER_UNVERIFIED && existing.fingerprint === fingerprint) { + return { acquired: false } as const; + } + if (run.issueId) await recovery.upsertSourceScoped({ companyId: input.companyId, sourceIssueId: run.issueId, kind: "active_run_watchdog", ownerType: "board", returnOwnerAgentId: run.agentId, - cause: "native_workspace_finalization_owner_unverified", - fingerprint: nativeSha256({ runId: input.runId, owner: rawPrior }), + cause: NATIVE_WORKSPACE_OWNER_UNVERIFIED, + fingerprint, evidence: { runId: input.runId, owner: rawPrior }, nextAction: "Verify the previous controller and its workspace-copyback processes have stopped, then release only this exact workspace owner receipt using doc/native-workspace-finalization-recovery.md. Resume workspace finalization without another provider turn.", wakePolicy: null, maxAttempts: 1, supersedeOnIdentityChange: true, diff --git a/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts b/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts new file mode 100644 index 00000000000..71bc4f426c5 --- /dev/null +++ b/server/src/services/native-runtime/native-workspace-finalization-recovery-state.ts @@ -0,0 +1,24 @@ +import { and, eq } from "drizzle-orm"; +import { heartbeatRuns, type Db } from "@paperclipai/db"; +import { isUuidLike, type IssueRecoveryAction } from "@paperclipai/shared"; +import { nativeSha256 } from "./canonical.js"; + +export const NATIVE_WORKSPACE_OWNER_UNVERIFIED = "native_workspace_finalization_owner_unverified"; + +/** A terminal task does not prove that an older controller's copyback stopped. */ +export async function hasUnverifiedNativeWorkspaceFinalizationOwner( + db: Db, + action: Pick, +): Promise { + if (action.cause !== NATIVE_WORKSPACE_OWNER_UNVERIFIED) return false; + const runId = action.evidence?.runId; + if (typeof runId !== "string" || !isUuidLike(runId)) return false; + const [run] = await db.select({ profile: heartbeatRuns.runnerProfileJson }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, action.companyId), + eq(heartbeatRuns.id, runId), + eq(heartbeatRuns.nativeIssueId, action.sourceIssueId), + eq(heartbeatRuns.runtimeMode, "native"), + )).limit(1); + const owner = run?.profile?.nativeWorkspaceFinalizationOwner; + return owner != null && action.fingerprint === nativeSha256({ runId, owner }); +} diff --git a/server/src/services/recovery/service.ts b/server/src/services/recovery/service.ts index 0a45ab1619b..e4c0f0eacc9 100644 --- a/server/src/services/recovery/service.ts +++ b/server/src/services/recovery/service.ts @@ -93,6 +93,7 @@ import { emitAgentTaskRun } from "../agent-task-run-telemetry.js"; import { budgetService } from "../budgets.js"; import { unadmittedChatWakeupCondition } from "../durable-chat-wakeup.js"; import { issueRecoveryActionService } from "../issue-recovery-actions.js"; +import { hasUnverifiedNativeWorkspaceFinalizationOwner } from "../native-runtime/native-workspace-finalization-recovery-state.js"; import { legacyExecutionNeedsReconciliationWithEvidence, terminalizeLegacyExecution, @@ -3428,6 +3429,10 @@ export function recoveryService( issueIds: [] as string[], }; for (const { action, issue } of rows) { + if (await hasUnverifiedNativeWorkspaceFinalizationOwner(db, action)) { + result.skipped += 1; + continue; + } const wakePolicy = parseObject(action.wakePolicy); const wakePolicyType = readNonEmptyString(wakePolicy.type); if ( diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index a4f0f95dd06..aa492179608 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -131,6 +131,8 @@ If already checked out by you, returns normally. If owned by another agent: `409 **Step 6 — Understand context.** Prefer `GET /api/issues/{issueId}/heartbeat-context` first. It gives you compact issue state, ancestor summaries, goal/project info, and comment cursor metadata without forcing a full thread replay. +For attribution questions, read `GET /api/issues/{issueId}/activity`. Sandbox agents can use this issue-scoped route subject to the same company and issue-read authorization. Automatic recovery cleanup is system activity, not evidence that the person viewing a task participated in it. + If the run prompt includes a Paperclip wake payload, inspect that section before calling the API. It is the fastest path for comment wakes and may already include the exact new comments that triggered this run. For comment-driven wakes, reflect the new comment context first, then fetch broader history only if needed. Use comments incrementally: @@ -708,6 +710,7 @@ If `plan` already exists, first `GET /api/issues/{issueId}/documents/plan` and r | Checkout task | `POST /api/issues/:issueId/checkout` | | Get task + ancestors | `GET /api/issues/:issueId` | | Compact heartbeat context | `GET /api/issues/:issueId/heartbeat-context` | +| Issue activity and attribution | `GET /api/issues/:issueId/activity` | | Update task | `PATCH /api/issues/:issueId` (optional `comment` field) | | Get comments / delta / single | `GET /api/issues/:issueId/comments[?after=:commentId&order=asc]` • `/comments/:commentId` | | Add comment | `POST /api/issues/:issueId/comments` | diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index 596e0648e20..72e12f6a189 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -107,6 +107,12 @@ POST /api/companies/company-1/exports } ``` +**Issue Activity (`GET /api/issues/:issueId/activity`)** + +Returns the issue's activity history, including actor, action, timestamp, and sanitized details. Sandbox agents can call this read-only route for issues their identity may read. Company boundaries and issue-read authorization still apply; this does not grant company-wide audit access. + +An automatic stale-recovery cancellation has `action: "issue.recovery_action_resolved"` and `details.source: "source_revalidation"`. Page reads use `details.trigger: "read_projection"`. These cancellations belong to the system. Historical reader-attributed rows with that source and trigger do not count as inbox participation or recent issue activity. + ### Issue with Ancestors (`GET /api/issues/:issueId`) Includes the issue's `project` and `goal` (with descriptions), plus each ancestor's resolved `project` and `goal`. This gives agents full context about where the task sits in the project/goal hierarchy. From 56a0bf0d492006a0aa2d0b99bfe7d5b86f657f3a Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:32:33 -0700 Subject: [PATCH 14/37] Support test Cloud connector identities (#16033) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Its Cloud connector uses an environment-bound identity to call a hosted broker. > - The identity parser and enrollment flow accept development, staging, and production. > - A separate test broker needs its own environment identity. > - This pull request accepts test identities and maps the test broker to that environment. > - Known hosted brokers still reject identities from other environments. ## Linked Issues or Issue Description **What existing behavior does this improve?** Cloud connector configuration and self-host enrollment. **Current behavior** The app rejects an identity with environment `test`. Self-host enrollment infers development for the test broker. **Proposed behavior** Accept `test` identities at `https://test.paperclip.app`. Infer test during self-host enrollment. Reject development, staging, and production identities at that broker. Continue to reject test identities at the production and staging brokers. **Reason and benefit** Branch testing needs a separate broker so test identities and requests stay in their selected environment. The existing identity, enrollment, and signature flow can provide that isolation with a new allowed environment value. **Breaking changes** Existing environment values and broker checks keep their current behavior. Older app versions cannot use test connector identities. ## What Changed - Add `test` to managed and self-hosted connector environment types and validation, and to the board API enrollment status type. - Infer test from its hosted broker and enforce the matching environment. - Test enrollment persistence, signed request environment/audience claims, and broker mismatch rejection. - Document test broker configuration in the development guide. ## Verification - Connector, enrollment, and board API-client suites passed: 63 tests across three files. Board UI typecheck and token gates passed after synchronizing its enrollment response type. - `pnpm -r typecheck` and `pnpm build` passed, including after rebasing onto current master. - `pnpm test:run` completed its general server stage: 17,867 passed, 105 skipped, and 9 failed across 5 files. The runner stops at that failing stage, so it did not run subsequent workspace/serialized stages locally. All nine failures also reproduce on the unchanged base commit in a separate checkout. Five skill-related cases resolve an unrelated ancestor skills directory; two workspace cases accept a remote main branch that their fixture expects to be absent; two permission-cleanup cases return macOS `ENOTEMPTY` instead of the expected permission error. - Current-head GitHub CI passed, including full general and serialized test suites, typecheck, build, runner checks, and browser e2e. Greptile is 5/5 with no unresolved comments. - `git diff --check` and Gitleaks passed. ## Risks The new environment value is additive. The test broker must support test identities before use. This change preserves existing signature handling, managed identity overrides, and rejection of mismatched identities at known hosted brokers. A local full-suite pass is not claimed; the nine failures above also occur without this change. ## Model Used OpenAI Codex, GPT-6, with reasoning and tool execution. The exact serving model ID and context window are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [ ] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip Co-authored-by: Dotta <34892728+cryppadotta@users.noreply.github.com> --- doc/DEVELOPING.md | 8 +++++ ...perclip-cloud-connector-enrollment.test.ts | 29 +++++++++++++++++++ .../paperclip-cloud-connector-enrollment.ts | 9 ++++-- .../paperclip-cloud-connector.test.ts | 18 ++++++++++++ .../src/services/paperclip-cloud-connector.ts | 5 ++-- ui/src/api/tools.ts | 2 +- 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 0241771ebf5..36b4cdc2b7e 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2034,6 +2034,14 @@ pnpm secrets:migrate-inline-env --apply # apply migration ## Internal Connection Token Brokers +Paperclip Cloud connector identities support `development`, `test`, `staging`, +and `production`. To enroll a self-hosted instance against the test broker, set +`PAPERCLIP_CLOUD_CONNECTOR_BASE_URL=https://test.paperclip.app`; its environment +is inferred as `test`. Managed instances also receive +`PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT=test`. Standard Cloud broker hosts reject +identities for another environment, and enrollment keys remain local to the +selected broker. + Connection token exchanges carry a resolved parent credential, so their URLs are public-only by default even in local/private deployments. To intentionally use an internal broker, configure an exact hostname allowlist: diff --git a/server/src/services/paperclip-cloud-connector-enrollment.test.ts b/server/src/services/paperclip-cloud-connector-enrollment.test.ts index 6443eafef32..8d731f426ba 100644 --- a/server/src/services/paperclip-cloud-connector-enrollment.test.ts +++ b/server/src/services/paperclip-cloud-connector-enrollment.test.ts @@ -193,6 +193,35 @@ describe("Paperclip Cloud self-host enrollment", () => { expect(paperclipCloudConnectorEnrollmentStatus({ PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://my-staging.paperclip.app", })).toMatchObject({ environment: "staging" }); + expect(paperclipCloudConnectorEnrollmentStatus({ + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + })).toMatchObject({ environment: "test" }); + }); + + it.each(["development", "staging", "production"])("rejects %s identity at the test broker", (environment) => { + expect(() => paperclipCloudConnectorEnrollmentStatus({ + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: environment, + })).toThrow(/broker and environment do not match/); + }); + + it("persists test enrollment identity and signs requests for the test broker", async () => { + const origin = "https://tenant.test.paperclip.app"; + const request = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { + const body = JSON.parse(String(init?.body)); + if (String(input).endsWith("/enrollments")) { + expect(body).toMatchObject({ environment: "test", origin }); + return Response.json({ enrollmentId: "enroll-test", verificationUrl: "https://test.paperclip.app/connections/enroll?id=enroll-test", expiresAt: new Date(Date.now() + 60_000).toISOString() }, { status: 201 }); + } + const claims = JSON.parse(Buffer.from(body.request.split(".")[1], "base64url").toString("utf8")); + expect(claims).toMatchObject({ env: "test", aud: "https://test.paperclip.app/v1/connector/enrollment-claims" }); + return Response.json({ id: loadPaperclipCloudConnectorIdentity()!.instanceId, environment: "test", origins: [origin] }); + }); + const env = { PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app" }; + await startPaperclipCloudConnectorEnrollment({ origin, env, request: request as typeof fetch }); + const pending = loadPaperclipCloudConnectorIdentity()!; + await completePaperclipCloudConnectorEnrollment({ enrollmentId: "enroll-test", approvalCode: "approval-code", state: pending.pending!.returnState, env, request: request as typeof fetch }); + expect(paperclipCloudConnectorConfigFromEnv(env)).toMatchObject({ environment: "test", baseUrl: "https://test.paperclip.app", instanceId: pending.instanceId }); }); it("rotates a non-active identity instead of mixing enrollment targets", async () => { diff --git a/server/src/services/paperclip-cloud-connector-enrollment.ts b/server/src/services/paperclip-cloud-connector-enrollment.ts index ba5de762ba0..0fbbd9755c7 100644 --- a/server/src/services/paperclip-cloud-connector-enrollment.ts +++ b/server/src/services/paperclip-cloud-connector-enrollment.ts @@ -17,7 +17,7 @@ const IDENTITY_VERSION = 1; const ENROLLMENT_FILE = "paperclip-cloud-connector.json"; const REQUEST_TYPE = "paperclip-cloud-connector-request+jwt"; -export type LocalConnectorEnvironment = "development" | "staging" | "production"; +export type LocalConnectorEnvironment = "development" | "test" | "staging" | "production"; type PendingEnrollment = { enrollmentId: string; @@ -350,10 +350,13 @@ function connectorEnvironment( ? "production" : host === "my-staging.paperclip.app" ? "staging" - : "development"; + : host === "test.paperclip.app" + ? "test" + : "development"; const value = env.PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT?.trim() || fallback || inferred; if (!isEnvironment(value)) throw new Error("Paperclip Cloud connector environment is invalid"); if ((host === "my.paperclip.app" && value !== "production") + || (host === "test.paperclip.app" && value !== "test") || (host === "my-staging.paperclip.app" && value !== "staging")) { throw new Error("Paperclip Cloud connector broker and environment do not match"); } @@ -390,7 +393,7 @@ function hasManagedConnectorIdentityOverride(env: NodeJS.ProcessEnv): boolean { } function isEnvironment(value: unknown): value is LocalConnectorEnvironment { - return value === "development" || value === "staging" || value === "production"; + return value === "development" || value === "test" || value === "staging" || value === "production"; } function normalizeBrokerOrigin(value: string): string { diff --git a/server/src/services/paperclip-cloud-connector.test.ts b/server/src/services/paperclip-cloud-connector.test.ts index a2a0b37abe0..999236c6c54 100644 --- a/server/src/services/paperclip-cloud-connector.test.ts +++ b/server/src/services/paperclip-cloud-connector.test.ts @@ -55,6 +55,24 @@ function config() { } describe("Paperclip Cloud connector", () => { + it("accepts a managed test identity and rejects other environments at its broker", () => { + const identity = config().config; + const env = { + PAPERCLIP_CLOUD_CONNECTOR_INSTANCE_ID: identity.instanceId, + PAPERCLIP_CLOUD_CONNECTOR_SIGN_PRIVATE_KEY: identity.signPrivateKey, + PAPERCLIP_CLOUD_CONNECTOR_SEAL_PRIVATE_KEY: identity.sealPrivateKey, + PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: "https://test.paperclip.app", + PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: "test", + }; + expect(paperclipCloudConnectorConfigFromEnv(env)).toMatchObject({ environment: "test", baseUrl: env.PAPERCLIP_CLOUD_CONNECTOR_BASE_URL }); + for (const environment of ["development", "staging", "production"]) { + expect(() => paperclipCloudConnectorConfigFromEnv({ ...env, PAPERCLIP_CLOUD_CONNECTOR_ENVIRONMENT: environment })).toThrow(/broker and environment do not match/); + } + for (const baseUrl of ["https://my.paperclip.app", "https://my-staging.paperclip.app"]) { + expect(() => paperclipCloudConnectorConfigFromEnv({ ...env, PAPERCLIP_CLOUD_CONNECTOR_BASE_URL: baseUrl })).toThrow(/broker and environment do not match/); + } + }); + async function rejection(response: Response) { const connector = createPaperclipCloudConnector({ config: config().config, diff --git a/server/src/services/paperclip-cloud-connector.ts b/server/src/services/paperclip-cloud-connector.ts index 241ff937b57..3f330292dab 100644 --- a/server/src/services/paperclip-cloud-connector.ts +++ b/server/src/services/paperclip-cloud-connector.ts @@ -32,7 +32,7 @@ export const GMAIL_CONNECTOR_SCOPES = [ ] as const; export { GOOGLE_WORKSPACE_CONNECTOR_PROFILES }; -export type PaperclipCloudConnectorEnvironment = "development" | "staging" | "production"; +export type PaperclipCloudConnectorEnvironment = "development" | "test" | "staging" | "production"; export type PaperclipCloudConnectorOperation = "status" | "session" | "claim" | "refresh" | "revoke" | "webhook-bind" | "event-lease" | "event-ack" | "github-app"; export type PaperclipCloudConnectorProfileId = GoogleWorkspaceConnectorProfileId | GitHubConnectorProfileId | AsanaConnectorProfileId | "slack.manager" | "github.bot"; export type PaperclipCloudConnectorProvider = "google" | "github" | "asana" | "slack"; @@ -232,7 +232,7 @@ export function paperclipCloudConnectorConfigFromEnv( if (values.some((value) => !value)) { throw new PaperclipCloudConnectorError("Paperclip Cloud connector configuration is incomplete", "CONNECTOR_CONFIG_INCOMPLETE"); } - if (environment !== "development" && environment !== "staging" && environment !== "production") { + if (environment !== "development" && environment !== "test" && environment !== "staging" && environment !== "production") { throw new PaperclipCloudConnectorError("Paperclip Cloud connector environment is invalid", "CONNECTOR_CONFIG_INVALID"); } const parsedBaseUrl = new URL(baseUrl); @@ -244,6 +244,7 @@ export function paperclipCloudConnectorConfigFromEnv( } const brokerHost = parsedBaseUrl.hostname.toLowerCase(); if ((brokerHost === "my.paperclip.app" && environment !== "production") + || (brokerHost === "test.paperclip.app" && environment !== "test") || (brokerHost === "my-staging.paperclip.app" && environment !== "staging")) { throw new PaperclipCloudConnectorError( "Paperclip Cloud connector broker and environment do not match", diff --git a/ui/src/api/tools.ts b/ui/src/api/tools.ts index 43cb010466d..1702478e54d 100644 --- a/ui/src/api/tools.ts +++ b/ui/src/api/tools.ts @@ -106,7 +106,7 @@ export type CloudConnectorEnrollmentStatus = { status: "not_configured" | "unenrolled" | "pending" | "active" | "suspended" | "unverified"; brokerBaseUrl: string; instanceId: string | null; - environment: "development" | "staging" | "production"; + environment: "development" | "test" | "staging" | "production"; origins: string[]; verificationUrl?: string; expiresAt?: string; From 7d2bb5aaabf633eb41c132e0f18fae820e303c09 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 19:43:04 -0700 Subject: [PATCH 15/37] docs(release): stable notes for the 2026.1012.1-beta.0 soak (v2026.1015.0) (#16053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The release process promotes a soaked beta to stable. The stable GitHub Release body comes from `releases/beta/v.md` on `master` > - The `draft_stable_notes` job pushed a commit-log skeleton to this branch when `2026.1012.1-beta.0` published today (workflow run 38311557959) > - The skeleton is grouped commit subjects. It is not in release-notes voice and it does not tell a self-hoster what to do before an upgrade > - This pull request replaces the skeleton with curated notes. Each claim traces to a pull request in `v2026.1012.0..1b4c8038` > - The benefit is that the earliest stable promotion (v2026.1015.0) finds finished notes on `master` on time ## Linked Issues or Issue Description **Issue type** Missing content **Where is the issue?** `releases/beta/v2026.1012.1-beta.0.md` — the stable-notes file for the `2026.1012.1-beta.0` soak. It exists only on this machine-owned branch and only as the auto-generated skeleton. **What's wrong?** The stable promotion reads this file from `master` and publishes it as the GitHub Release body. Until this branch merges, the stable preflight has no notes to resolve. The skeleton lists raw commit subjects with nested PR summaries. It does not call out the three changes that need operator attention before the upgrade. **Suggested fix** Merge the curated notes so the notes invariant holds for the v2026.1015.0 promotion. Correct the `> Released:` date in a follow-up if the promotion date slips. ## What Changed - Replaced the skeleton in `releases/beta/v2026.1012.1-beta.0.md` with curated stable notes for v2026.1015.0 in the same layout as `releases/beta/v2026.1006.0-beta.0.md`: overview, Breaking Changes, Highlights, Fixes, Improvements, Upgrade Guide, Contributors - Breaking Changes lists the expanded Operator role preset (#15377), GitHub review bots behind a new default-off experimental setting (#15416), and budget policies that hold on unpriced usage by default (#14997) - Upgrade Guide enumerates migrations `0306` through `0333` with one-phrase effects, the three new environment variables with defaults, the three new default-off experimental settings, the new agent identity key variables for custom adapters, and the private-task API behavior - Release and CI internals, smoke specs, canary tooling, Paperclip Cloud work, heartbeat refactors, dependency bumps, and test-only changes are left out. Features already described in the v2026.1012.0 notes are not repeated ## Verification - Beta publish is complete: npm dist-tag `beta` is `2026.1012.1-beta.0`, tag `beta/v2026.1012.1-beta.0` points at `1b4c803816e82b0265c8422e1a804327a7ed8c54`, and the `publish_beta`, `smoke_beta`, and `draft_stable_notes` jobs in run 38311557959 all succeeded - `git rev-list --count v2026.1012.0..1b4c803816e82b0265c8422e1a804327a7ed8c54` returns 200, which matches the overview and Contributors section - `git shortlog -sn --no-merges` over the same range shows 6 human authors after the two bot accounts are excluded. The two external handles were read from the GitHub commit authors, not guessed from names - `./scripts/release.sh stable --date 2026-10-15 --print-version` returns `2026.1015.0` - Every `#NNNN` link in the file resolves to a pull request inside the range. Migration numbers, setting keys, env var names, and defaults were checked in the code on the source commit, not in commit subjects - The file contains no internal ticket ids or instance-local links - The compare view for this branch shows one added file and no source changes ## Risks - Low risk: a single markdown file, no source changes. If the promotion date slips past 2026-10-15, the `> Released:` line and H1 need a one-line update before the stable dispatch. The beta-keyed filename makes that re-date harmless ## Model Used - Claude (Anthropic), model ID `claude-fable-5-1` (Claude Fable 5.1), extended thinking enabled, tool use via Claude Code ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- releases/beta/v2026.1012.1-beta.0.md | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 releases/beta/v2026.1012.1-beta.0.md diff --git a/releases/beta/v2026.1012.1-beta.0.md b/releases/beta/v2026.1012.1-beta.0.md new file mode 100644 index 00000000000..66ede73ab84 --- /dev/null +++ b/releases/beta/v2026.1012.1-beta.0.md @@ -0,0 +1,65 @@ +# Paperclip v2026.1015.0 + +> Released: 2026-10-15 + +Paperclip v2026.1015.0 carries 200 commits, promoted from `2026.1012.1-beta.0`. The headline is private work: a task or project can now be marked private and shared with named people and agents; everyone else cannot open it, find it in task lists or search, read its run transcripts, or reach it through a plugin. Around that: agents can run through your own model gateway (OpenRouter, Amazon Bedrock, Google Gemini, or any OpenAI Responses, Anthropic Messages, or Chat Completions endpoint), Slack bots set themselves up through Slack's Manifest API, the GitHub review bot gets a two-screen setup and a real **Paperclip Review** status check, Costs becomes an exact ledger that also shows monthly subscription fees, and connected assistants can edit tasks, upload files, and configure agents over MCP. Underneath, OpenAI Dot and Pi 1.0 join the experimental Runner, native OpenCode streams as it types, each agent gets a persistent cryptographic identity, and idle instances stop polling the database every second. + +## Breaking Changes + +- **Operators can now edit the company.** The Operator role preset grows from one grant (`tasks:assign`) to fourteen: agent creation and configuration, skills, environments, invitations, task assignment, pipelines, connections, tool management and use, and audit reads. Existing Operators pick up the missing grants through the normal startup seeding; custom grant scopes are kept. Join approval and member-permission management stay out of the preset, and inviting an Admin now requires join approval while inviting an Owner also requires member-permission management. Review your member roles if Operators were meant to be read-mostly. ([#15377](https://github.com/paperclipai/paperclip/pull/15377)) +- **GitHub review bots move behind their own experimental setting.** The **GitHub Code Review Bot** catalog entry, its setup, and its management screens appear only while **Settings → Experimental → GitHub review bots** (`enableGitHubReviewBots`, off by default) is on; existing bots keep running while it is off. The old setup-method and delegated-agent screens are gone, and per-person automatic-run flags are replaced by author include/exclude filters (legacy `people[].automaticReviews` values are read but ignored). ([#15416](https://github.com/paperclipai/paperclip/pull/15416)) +- **Budgets hold on unpriced usage by default.** Each budget policy gains an unpriced-usage setting that defaults to `block`: while a run's usage has no reliable price, the policy counts as at its limit until the charge is resolved through the audited cost adjustment flow. Set the policy to `allow` to keep running on unpriced usage. ([#14997](https://github.com/paperclipai/paperclip/pull/14997)) + +## Highlights + +- **Private tasks and projects** — Mark a task private when you create it, or make a project private with its own member list; a personal private project is created for you on first use. Private work is readable only by its responsible user, creator, current assignee, named grantees, and private-project members, and that rule applies to task lists and task counts, search, activity, run detail, transcripts, events, and logs, documents, attachments, work products, and every plugin read. Two surfaces stay company-wide on purpose: dashboard and sidebar totals may count private work without exposing a title or identifier, and the company run list shows a locked row for a private task's run with only the agent, status, timing, token, and cost fields, so budget oversight keeps working. Privacy flows down to children; sharing a child never exposes its parent or siblings. Owners and admins do not see private tasks by default and must request `?breakGlass=true`, which writes an audit row and a warning notice on the task. Agents' access is intersected with the run's responsible user, so reusing an agent does not carry another person's permissions. ([#14717](https://github.com/paperclipai/paperclip/pull/14717), [#10633](https://github.com/paperclipai/paperclip/pull/10633), [#14718](https://github.com/paperclipai/paperclip/pull/14718)) +- **Bring your own model gateway** — Connectors gains rows for OpenRouter, Amazon Bedrock, Google Gemini, and generic Responses API, Messages API, Chat Completions API, and local endpoints. URLs, protocol, region, and credentials live on the connection; the agent's Harness / Runtime page picks a compatible **Connection** from a dropdown and the model picker accepts manual IDs. Codex (legacy and Runner) routes to OpenRouter or custom Responses endpoints, Claude (legacy and Runner) to OpenRouter, custom Messages endpoints, or Bedrock, and OpenCode and Hermes to OpenRouter or custom Chat Completions endpoints. OpenRouter connections without an explicit model list load OpenRouter's public catalog. ([#14970](https://github.com/paperclipai/paperclip/pull/14970), [#15341](https://github.com/paperclipai/paperclip/pull/15341)) +- **Slack bots set themselves up** — New Slack bot drafts default to automatic setup: paste a temporary App configuration token, Paperclip creates the app through Slack's Manifest API, installs it through OAuth, links your Slack account, uploads the agent's avatar, and verifies delivery in a four-screen wizard with no Slack CLI involved ([#15413](https://github.com/paperclipai/paperclip/pull/15413)). A new **Require at-mention** setting stops thread replies from waking the agent without an explicit mention ([#15612](https://github.com/paperclipai/paperclip/pull/15612)); a follow-up sent after a Slack Stop starts a fresh turn instead of stalling ([#15737](https://github.com/paperclipai/paperclip/pull/15737)); and Slack agents now know the saved slash command people use to link their accounts, link every task they create, and report results and review requests back in the originating thread ([#15576](https://github.com/paperclipai/paperclip/pull/15576), [#15579](https://github.com/paperclipai/paperclip/pull/15579), [#15611](https://github.com/paperclipai/paperclip/pull/15611)). +- **A two-screen GitHub review bot** — Setup is now Choose agent, then Connect GitHub under a personal account or organization; GitHub asks for creation and installation consent, and Paperclip stores the App secrets locally, imports repository access, and verifies delivery. Each accepted request gets one editable App-owned progress comment, and review assessments publish a **Paperclip Review** check for the reviewed commit with a configurable passing score (1–5) that branch protection can require. A self-hosted instance needs one Paperclip Cloud approval before its first managed GitHub connection, because Cloud hosts the fixed OAuth callback and signed webhook inbox; tokens are sealed to your instance. ([#15416](https://github.com/paperclipai/paperclip/pull/15416)) +- **Costs you can reconcile** — Accounting is now a durable, exact ledger: usage receipts are fsynced and replayed after failures, retries settle one charge, amounts keep decimal cents, and every event is marked reported, estimated, or unpriced instead of silently zero ([#14991](https://github.com/paperclipai/paperclip/pull/14991), [#14997](https://github.com/paperclipai/paperclip/pull/14997)). The Costs page reports each connected ChatGPT or Claude subscription's monthly fee once beside API spend, lets you enter the real price, and keeps ambiguous plans unpriced ([#15634](https://github.com/paperclipai/paperclip/pull/15634)); quota reads are scoped to authorized company accounts ([#14994](https://github.com/paperclipai/paperclip/pull/14994)); cost gaps are listed by agent with links to the affected runs, the model the runtime actually selected is recorded, and a retry appears only when saved evidence is ready ([#15948](https://github.com/paperclipai/paperclip/pull/15948)); and historical pricing gaps show as a neutral note under the totals rather than a red error ([#15459](https://github.com/paperclipai/paperclip/pull/15459)). +- **Assistants that can do more over MCP** — A connected Claude Code, Codex, or other MCP assistant can now edit tasks, finish or block them, write documents and revisions, register deliverables, upload files through expiring tickets, and, with a separately consented configuration scope, manage agent settings and instructions, projects and repositories, and skills ([#15380](https://github.com/paperclipai/paperclip/pull/15380)). Connections are named by owner and client ("Dotta's Codex connection") with the authorizing person's avatar, revoked rows disappear, and the copied invitation is shorter ([#15411](https://github.com/paperclipai/paperclip/pull/15411)). +- **Sixteen more apps in the catalog** — Gauge ([#15596](https://github.com/paperclipai/paperclip/pull/15596)), Superagent ([#15394](https://github.com/paperclipai/paperclip/pull/15394)), Telem.AI web search ([#15379](https://github.com/paperclipai/paperclip/pull/15379)), Enterpret ([#13906](https://github.com/paperclipai/paperclip/pull/13906)), Calendly, Exa, Firecrawl, GSC Wizard, Parallel Search, Tavily, and Windsor.ai ([#15621](https://github.com/paperclipai/paperclip/pull/15621)), and Bitly, Context7, Hugging Face, and Resend ([#15686](https://github.com/paperclipai/paperclip/pull/15686)) join as verified hosted MCP connections. Sentry sign-in discovers OAuth through its MCP resource metadata, so setup no longer asks for a client ID and secret ([#15837](https://github.com/paperclipai/paperclip/pull/15837)), and OAuth grants now record whether each scope came from the provider or from Paperclip's request ([#14059](https://github.com/paperclipai/paperclip/pull/14059)). +- **New Runner providers** — OpenAI Dot joins as a standalone experimental agent choice: pair your Dot with a one-use code, and the Runner delivers assignments over MCP Events while Dot uses its own tools and reports back ([#15402](https://github.com/paperclipai/paperclip/pull/15402), [#15414](https://github.com/paperclipai/paperclip/pull/15414), [#15581](https://github.com/paperclipai/paperclip/pull/15581), [#15734](https://github.com/paperclipai/paperclip/pull/15734)). Pi 1.0 runs through the experimental Runner with explicit provider and model selection ([#14921](https://github.com/paperclipai/paperclip/pull/14921)), and the Cursor Runner integration is consolidated onto mainline recovery behavior with a public install path ([#15075](https://github.com/paperclipai/paperclip/pull/15075)). +- **Codex and OpenCode that just start** — Native Codex uses the installed Codex CLI and accepts usable older or newer versions, so a clean npm install no longer needs a global command ([#15555](https://github.com/paperclipai/paperclip/pull/15555)); a model that the sandbox's Codex CLI is too old for is swapped for a compatible one with a visible warning instead of failing every turn ([#15399](https://github.com/paperclipai/paperclip/pull/15399), [#15519](https://github.com/paperclipai/paperclip/pull/15519)); saved runs survive server upgrades ([#15518](https://github.com/paperclipai/paperclip/pull/15518)); and task environment variables reach later turns and tool commands ([#15451](https://github.com/paperclipai/paperclip/pull/15451)). Native OpenCode streams text as it arrives ([#15708](https://github.com/paperclipai/paperclip/pull/15708)), starts without the postinstall script that package managers skip ([#15702](https://github.com/paperclipai/paperclip/pull/15702)), and selects the bundled Linux assets for remote runs without path overrides ([#15721](https://github.com/paperclipai/paperclip/pull/15721)). +- **Reports you can open on the task** — HTML attachments and workspace `.html` files render in an opaque-origin sandbox with view-source and download controls ([#15447](https://github.com/paperclipai/paperclip/pull/15447)), and CSV files open as tables with row numbers, sticky headers, and record counts ([#15445](https://github.com/paperclipai/paperclip/pull/15445)). +- **Clearer task provenance and waits** — A follow-up task an agent creates while working on a task now defaults to that task as its parent, and the task page shows "Created from by " ([#15762](https://github.com/paperclipai/paperclip/pull/15762)); native agents can schedule their own one-shot task monitor and yield until it fires ([#15446](https://github.com/paperclipai/paperclip/pull/15446)); the monitor card gets an X with a confirmation ([#15735](https://github.com/paperclipai/paperclip/pull/15735)); and a saved pull request stays visible in Properties with its review request and a **Check status** action even when the provider lookup fails ([#15375](https://github.com/paperclipai/paperclip/pull/15375)). +- **Your primary agent** — Each person gets one primary agent per company, shown with a crown on the roster and used as the fallback for new tasks and Chat when there is no recent choice; the first agent you create becomes primary automatically and can be changed on the agent profile ([#15470](https://github.com/paperclipai/paperclip/pull/15470)). +- **Company decision models and fast responses** — Company Settings can bind one shared OpenAI or OpenRouter API connection for small typed model decisions, with permission checks, budget reservations, and a Costs → Decisions history ([#15473](https://github.com/paperclipai/paperclip/pull/15473)). On top of it, **Experimental fast responses** posts a short acknowledgement comment within three seconds of a human message while the agent starts its normal run, in tasks, Agent Chat, and Slack and GitHub threads ([#15748](https://github.com/paperclipai/paperclip/pull/15748)). + +## Fixes + +- **Workspaces and sandboxes** — A transient sandbox copy-back failure is retried up to three times before the task is held for repair, and remaining holds explain what happened in plain language ([#15920](https://github.com/paperclipai/paperclip/pull/15920)); a remote sandbox whose required copy-back failed is kept, with its files, behind an explicit repair decision instead of being deleted by cleanup ([#15479](https://github.com/paperclipai/paperclip/pull/15479), [#15790](https://github.com/paperclipai/paperclip/pull/15790)); server shutdown waits for a legacy adapter's workspace export before releasing its sandbox lease ([#15857](https://github.com/paperclipai/paperclip/pull/15857)); a reused saved worktree keeps its original repository when project settings change ([#15528](https://github.com/paperclipai/paperclip/pull/15528)); an accepted native result is no longer mistaken for lost work by the orphan reaper after a restart ([#15718](https://github.com/paperclipai/paperclip/pull/15718)); OpenCode runs in the realized workspace via `--dir` ([#11438](https://github.com/paperclipai/paperclip/pull/11438)); Daytona OpenCode sessions keep their deadlines ([#15905](https://github.com/paperclipai/paperclip/pull/15905)); and OpenCode timeouts no longer claim the configured deadline expired ([#15789](https://github.com/paperclipai/paperclip/pull/15789)). +- **Runs, recovery, and questions** — A retry no longer burns itself while a stopped run's environment is still cleaning up, and settled repair instructions stop reappearing in later wakes ([#15463](https://github.com/paperclipai/paperclip/pull/15463)); a review wait whose automatic repair also leaves no wake path now records a board decision instead of sitting silently ([#15764](https://github.com/paperclipai/paperclip/pull/15764)); a parent is woken again when a child finishes requested revisions ([#15372](https://github.com/paperclipai/paperclip/pull/15372)); native runs resume cleanly after connection approval decisions with final usage retained ([#15471](https://github.com/paperclipai/paperclip/pull/15471), [#15420](https://github.com/paperclipai/paperclip/pull/15420), [#15514](https://github.com/paperclipai/paperclip/pull/15514)); a pending tool request expires on its own when the user has already connected the service in Apps ([#15856](https://github.com/paperclipai/paperclip/pull/15856)); agents stop re-asking questions that moved into feed history ([#15392](https://github.com/paperclipai/paperclip/pull/15392)); archiving a task during its completion no longer deadlocks ([#15615](https://github.com/paperclipai/paperclip/pull/15615)); and a task policy with a monitor but no stages no longer crashes the task page ([#15593](https://github.com/paperclipai/paperclip/pull/15593)). +- **Adapters** — Claude CLI sessions resume across per-run agent file copies ([#15437](https://github.com/paperclipai/paperclip/pull/15437)); Hermes gets managed instructions through its system overlay instead of a copy in every resumed user turn ([#15439](https://github.com/paperclipai/paperclip/pull/15439)); and external agents such as Dot are told to use their own tools when a Paperclip app is missing ([#15734](https://github.com/paperclipai/paperclip/pull/15734)). +- **Connections and MCP** — One oversized or malformed MCP reply fails only that call instead of locking the whole connection in an error state ([#15462](https://github.com/paperclipai/paperclip/pull/15462)); remote MCP callbacks keep an explicitly saved empty agent selection, token exchange sends `Accept: application/json`, and saved hidden-provider drafts resume from Finish setup ([#15686](https://github.com/paperclipai/paperclip/pull/15686)); Airtable, Calendly, and other verified providers get the registration and form fixes found in live testing ([#15621](https://github.com/paperclipai/paperclip/pull/15621)); and the sidebar keeps its last plugin items quiet when the server is unreachable ([#15588](https://github.com/paperclipai/paperclip/pull/15588)). + +## Improvements + +- Hiring, approval, pause, resume, and termination run through a durable agent lifecycle with explicit states: proposed hires stay pending until approval, approved hires are prepared and have their harness configuration verified before work, required plugins get callbacks at each step, and the agent page shows lifecycle progress with a retry action ([#15631](https://github.com/paperclipai/paperclip/pull/15631), [#15391](https://github.com/paperclipai/paperclip/pull/15391)); plugins can consume a durable resource lifecycle journal with acknowledgments, seeded with a one-time baseline of existing agents and projects, and project archive and restore now write journal events ([#15343](https://github.com/paperclipai/paperclip/pull/15343), [#15371](https://github.com/paperclipai/paperclip/pull/15371)). +- Every agent gets a persistent Ed25519 keypair, created on hire or on its next managed run, encrypted at rest and handed to managed processes as `PAPERCLIP_AGENT_KEY_ID`, `PAPERCLIP_AGENT_PUBLIC_KEY`, and `PAPERCLIP_AGENT_PRIVATE_KEY`; the public key appears on the agent overview ([#15352](https://github.com/paperclipai/paperclip/pull/15352)). +- Agents can file complaints and suggestions about their tooling into the instance's `agent_commentary` table through new `complain` and `suggestion-box` skills or native `submit_complaint` and `submit_suggestion` tools, keeping that feedback out of task threads ([#15367](https://github.com/paperclipai/paperclip/pull/15367)). +- Idle instances go quiet: chat delivery, provider action, Slack sync, email, Browser Use cleanup, and five server delivery queues now wake from committed writes and known deadlines instead of scanning the database every second or three ([#15625](https://github.com/paperclipai/paperclip/pull/15625), [#15750](https://github.com/paperclipai/paperclip/pull/15750), [#15757](https://github.com/paperclipai/paperclip/pull/15757), [#15802](https://github.com/paperclipai/paperclip/pull/15802), [#15808](https://github.com/paperclipai/paperclip/pull/15808), [#15822](https://github.com/paperclipai/paperclip/pull/15822)); hosts that park idle instances get an idle-sleep safety report that fences accepted work, drains plugin workers, and treats finished logins, consumed retries, and verified delivery history as history rather than pending work ([#15522](https://github.com/paperclipai/paperclip/pull/15522), [#15599](https://github.com/paperclipai/paperclip/pull/15599), [#15761](https://github.com/paperclipai/paperclip/pull/15761), [#15851](https://github.com/paperclipai/paperclip/pull/15851), [#15897](https://github.com/paperclipai/paperclip/pull/15897), [#15907](https://github.com/paperclipai/paperclip/pull/15907)), with an opt-in final backup checkpoint before sleep ([#15629](https://github.com/paperclipai/paperclip/pull/15629)). +- Task notifications show the actor's name and avatar and hide your own actions ([#15695](https://github.com/paperclipai/paperclip/pull/15695)); other people's comments in task chat appear on the left with their name and avatar, and only yours are on the right in blue ([#15494](https://github.com/paperclipai/paperclip/pull/15494)); an agent blocked by an AI connection its responsible user cannot use shows an expanded error naming the agent and connection with a link to its AI settings ([#15741](https://github.com/paperclipai/paperclip/pull/15741)); and a teammate without the agent's required personal AI account can connect their own from inside the chat and resume the message ([#15376](https://github.com/paperclipai/paperclip/pull/15376)). +- The task composer keeps its model label steady while the effort slider moves and the slider is larger ([#15444](https://github.com/paperclipai/paperclip/pull/15444)), nested selectors scroll with the wheel and touch ([#15374](https://github.com/paperclipai/paperclip/pull/15374)), the project picker always offers **No project** ([#15382](https://github.com/paperclipai/paperclip/pull/15382)), routine descriptions keep code-block pastes in the code block and gain a copy control ([#15773](https://github.com/paperclipai/paperclip/pull/15773)), confirmation cards stack the prompt above the target pill and hide unlabeled machine keys ([#15895](https://github.com/paperclipai/paperclip/pull/15895)), chat connection and recovery cards share one surface ([#15771](https://github.com/paperclipai/paperclip/pull/15771)), the Slack chat-or-tools chooser gets icons ([#15574](https://github.com/paperclipai/paperclip/pull/15574)), the Workspace filter leaves the task and inbox filter menus ([#15788](https://github.com/paperclipai/paperclip/pull/15788)), and the star and leave buttons leave the agent index ([#15400](https://github.com/paperclipai/paperclip/pull/15400)). +- Tasks whose starting branch does not exist in the repository show a branch repair card that offers the remote's default branch or a typed branch and retries through the normal admission gates ([#15726](https://github.com/paperclipai/paperclip/pull/15726)); a GitHub reference the instance's credentials cannot see is reported as inaccessible and re-checked instead of frozen as "Not found" ([#15906](https://github.com/paperclipai/paperclip/pull/15906)); and the Paperclip skill ships its own issue-update helper and honors a harness checkout instead of demanding a second one ([#15554](https://github.com/paperclipai/paperclip/pull/15554), [#15548](https://github.com/paperclipai/paperclip/pull/15548)). +- Workspace jobs that exit non-zero return `422 workspace_job_failed` with the job's own output instead of a 500 ([#15941](https://github.com/paperclipai/paperclip/pull/15941)); custom source launchers get the native Runner WebSocket transport from the shared HTTP server constructor ([#15682](https://github.com/paperclipai/paperclip/pull/15682)); a refused local Hermes Gateway connection explains which host to check ([#15520](https://github.com/paperclipai/paperclip/pull/15520)); and pi-local's unavailable-model error no longer dumps the model catalog ([#13628](https://github.com/paperclipai/paperclip/pull/13628)). +- Failure reports carry bounded evidence for Daytona acquisition timeouts and provider state, workspace base-ref resolution, sandbox transfer and sync-out errors, orphan process loss, ACP bridge terminal frames, Git worktree inspection, and stackless browser rejections ([#15946](https://github.com/paperclipai/paperclip/pull/15946), [#15736](https://github.com/paperclipai/paperclip/pull/15736), [#15719](https://github.com/paperclipai/paperclip/pull/15719), [#15637](https://github.com/paperclipai/paperclip/pull/15637), [#15481](https://github.com/paperclipai/paperclip/pull/15481), [#15475](https://github.com/paperclipai/paperclip/pull/15475), [#15478](https://github.com/paperclipai/paperclip/pull/15478), [#15525](https://github.com/paperclipai/paperclip/pull/15525), [#15940](https://github.com/paperclipai/paperclip/pull/15940)), while proven configuration blockers such as a missing user secret, an unchosen AI account, a credential not shared with the responsible user, a local-directory project with a worktree policy, or an external service outage stay out of Sentry ([#15472](https://github.com/paperclipai/paperclip/pull/15472), [#15618](https://github.com/paperclipai/paperclip/pull/15618), [#15714](https://github.com/paperclipai/paperclip/pull/15714), [#15521](https://github.com/paperclipai/paperclip/pull/15521), [#15590](https://github.com/paperclipai/paperclip/pull/15590)). + +## Upgrade Guide + +- **Migrations `0306`–`0333`** run on upgrade: `0306` allows `google` in the AI account-default provider checks; `0307` rewrites the lifecycle-event action constraint; `0308` adds `agent_identity_keys`; `0309` adds `plugin_lifecycle_acknowledgments`, indexes the lifecycle journal, and seeds a one-time baseline event per existing agent and project; `0310` adds `agent_commentary`; `0311` adds MCP attachment upload and file ticket tables; `0312` adds accounting receipt, reservation, adjustment, invoice, and provider snapshot tables, widens cost columns to `numeric`, and adds the budget unpriced-usage policy (default `block`); `0313` adds `issue_access_grants` and `project_access_members`, adds `visibility` to tasks and projects (default `open`), and backfills privacy parent edges and run bindings in 1,000-row batches; `0314` binds historical inline draft images to their first task; `0315` adds `user_company_preferences` and backfills a primary agent from history; `0316` adds decision model tables; `0317` adds OpenAI Dot binding, mailbox, and assignment tables; `0318` adds `chat_slack_registrations`; `0319` makes Dot agent refresh tokens non-expiring; `0320` adds agent lifecycle columns and derives `lifecycle_state` from each agent's current status; `0321` adds `chat_endpoints.require_at_mention`; `0322` adds AI subscription account and price tables; `0323` adds GitHub registration owner columns; `0324`–`0330` add the experimental voice session tables; `0331` adds fast-response tables and `issue_comments.origin`; `0332` adds a question-history index concurrently; `0333` adds cost data-quality indexes. `0313` and `0314` run outside a single transaction and commit each batch, so an interrupted upgrade leaves them pending and safe to rerun; expect them to take longer on instances with large run histories. +- **New environment variables**, none required: `PAPERCLIP_ISSUE_PRIVACY_MODE` (default `enforce`; `shadow` logs would-deny decisions without enforcing, `off` disables the predicate) ([#10633](https://github.com/paperclipai/paperclip/pull/10633)); `PAPERCLIP_DB_BACKUP_IDLE_CHECKPOINT_ENABLED=1` opts an instance with automatic backups into taking a final verified backup before idle sleep (off by default) ([#15629](https://github.com/paperclipai/paperclip/pull/15629)); `PAPERCLIP_RESPONSIBLE_USER_AUTHZ_CACHE_TTL_MS` bounds the responsible-user permission snapshot (default `5000`) ([#10633](https://github.com/paperclipai/paperclip/pull/10633)). +- **New experimental settings**, all off by default: **GitHub review bots** (`enableGitHubReviewBots`) shows GitHub bot setup and management ([#15416](https://github.com/paperclipai/paperclip/pull/15416)); **OpenAI Dot** (`enableOpenAiDot`) adds Dot as an agent choice and requires **Assistant connections (MCP)**, authenticated sign-in, and an HTTPS `PAPERCLIP_PUBLIC_URL` ([#15414](https://github.com/paperclipai/paperclip/pull/15414)); **Experimental fast responses** (`enableFastResponses`) reveals Company Settings → Connections and allows acknowledgement comments ([#15748](https://github.com/paperclipai/paperclip/pull/15748)). Decision model setup lives under Company Settings → General until fast responses are on ([#15473](https://github.com/paperclipai/paperclip/pull/15473)). +- **Review Operator members** ([#15377](https://github.com/paperclipai/paperclip/pull/15377)): Operators now hold company editing, invitation, connection, tool, and audit grants after startup seeding. Demote members who should not configure agents or connect accounts. +- **Budget policies and unpriced usage** ([#14997](https://github.com/paperclipai/paperclip/pull/14997)): existing policies get `unpricedUsagePolicy: block`. If your agents run on providers whose prices Paperclip cannot determine, set the policy to `allow` or resolve unpriced events through the cost adjustment flow so the budget does not hold them. +- **Custom adapters and managed processes** ([#15352](https://github.com/paperclipai/paperclip/pull/15352)): managed runs now receive `PAPERCLIP_AGENT_KEY_ID`, `PAPERCLIP_AGENT_PUBLIC_KEY`, and `PAPERCLIP_AGENT_PRIVATE_KEY`; the key values are multiline PEM strings and the private key must not be logged. The wrapping key is `PAPERCLIP_SECRETS_MASTER_KEY` or the local secrets master-key file; a wrong master key fails the run rather than regenerating the keypair. +- **Private-task callers** ([#10633](https://github.com/paperclipai/paperclip/pull/10633)): direct reads of a private task by a non-member return `404`; owners and admins must pass `?breakGlass=true` for an audited emergency read; plugins are non-member principals and see no private content. +- **Slack and GitHub drafts**: existing Slack bot drafts without a saved setup method stay on manual setup ([#15413](https://github.com/paperclipai/paperclip/pull/15413)); existing GitHub bots, direct webhooks, and manual existing-App recovery keep working, but their settings are hidden until **GitHub review bots** is enabled ([#15416](https://github.com/paperclipai/paperclip/pull/15416)). +- **Harness pins** are unchanged for Codex, Claude, and OpenCode. Native Codex now prefers the Codex CLI installed with the server package and tolerates version drift; a sandbox image whose Codex CLI is too old for the selected model is switched to a compatible model with a warning ([#15555](https://github.com/paperclipai/paperclip/pull/15555), [#15519](https://github.com/paperclipai/paperclip/pull/15519)). +- No other configuration or API changes require action. + +## Contributors + +This release has 200 commits from 6 contributors. Thank you to everyone who contributed to this release! + +@lorenzozanee, @lusoris From e168512f7bdbb6af6ea01954c6d7ed80b91e903e Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:49:47 -0500 Subject: [PATCH 16/37] perf(chat): stop polling failed run retries (#15871) Stop one-second scans of failed chat-run retries. Wake the existing retry queue after transaction commits and at saved retry or stale-claim deadlines. Preserve claim fences, authorization, recovery, and idempotent agent admission. Resolve conflicts with the current chat-service extraction by applying the retry signals and shared deadline predicate in the failed-run retry module. Verified with pnpm typecheck, pnpm build, all 1,328 chat and coordinator regression tests, and current-head CI. Greptile reviewed the final head at 5/5 with no unresolved threads. Co-Authored-By: Paperclip --- ...-instance-sleep-and-scheduler-inventory.md | 9 +- .../app-private-hostname-gate.test.ts | 1 + server/src/__tests__/chat-action-work.test.ts | 5 +- .../chat-channels.integration.test.ts | 96 +++++++++++++++++++ server/src/services/chat-action-work.ts | 24 +++-- server/src/services/chat-channels.ts | 5 +- .../chat-channels/failed-run-retry.ts | 71 ++++++-------- .../src/services/chat-work-notifications.ts | 1 + .../services/delivery-work-notifications.ts | 1 + 9 files changed, 157 insertions(+), 56 deletions(-) diff --git a/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md b/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md index e40716791f3..8177461f43b 100644 --- a/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md +++ b/doc/plans/2026-10-08-instance-sleep-and-scheduler-inventory.md @@ -444,8 +444,13 @@ Longer polling intervals alone do not complete an item. running sessions and pauses without rescheduling until an endpoint change. Tests exercise real producers with deferred fast paths disabled, retry and startup recovery, stale-send quarantine, and zero scans after settlement. - Remaining **1s** maintenance: provider runtimes, Telegram maintenance, failed - chat-run retries, run milestone/wake-notice projection, GitHub operation + Failed chat-run retries now also use commit wakes and durable deadlines from + existing action rows. Issued retries honor `retryAt`; processing claims wait + for both stale-claim expiry and any saved retry time. Startup recovers unfinished + requests. Claim and settlement writes signal in their own short transactions, + with agent wakeup outside those transactions. Empty and terminal queues disarm. + Remaining **1s** maintenance: provider runtimes, Telegram maintenance, + run milestone/wake-notice projection, GitHub operation recovery, and Teams protocol maintenance. GitHub pre-ingress recovery must still inspect provider history: a lost callback has no local queue row. This slice does **not** yet make the whole chat subsystem quiet. Independent diff --git a/server/src/__tests__/app-private-hostname-gate.test.ts b/server/src/__tests__/app-private-hostname-gate.test.ts index 8f087241913..1c46075d2aa 100644 --- a/server/src/__tests__/app-private-hostname-gate.test.ts +++ b/server/src/__tests__/app-private-hostname-gate.test.ts @@ -110,6 +110,7 @@ describe("createChatReconciliationCoordinator", () => { const service = readFileSync(new URL("../services/chat-channels.ts", import.meta.url), "utf8"); const actionMaintenance = service.slice(service.indexOf("async function processPendingChatMaintenance("), service.indexOf("async function processPendingDeliveries(")); + expect(actionMaintenance).not.toContain("processFailedChatRunRetries("); expect(actionMaintenance).not.toContain("processPendingProviderEffects("); expect(actionMaintenance).not.toContain("processPendingGitHubWebhookIngress("); }); diff --git a/server/src/__tests__/chat-action-work.test.ts b/server/src/__tests__/chat-action-work.test.ts index b96541e79ee..294715f922f 100644 --- a/server/src/__tests__/chat-action-work.test.ts +++ b/server/src/__tests__/chat-action-work.test.ts @@ -19,6 +19,7 @@ function setup() { const coordinator = createDeliveryWorkCoordinator({ owner, canRun: enabled, canReconcile: enabled, onError }); coordinators.push(coordinator); const service = { + processFailedChatRunRetries: vi.fn(async () => {}), processPendingProviderEffects: vi.fn(async () => {}), processPendingGitHubWebhookIngress: vi.fn(async () => {}), processPendingSlackSessionSyncs: vi.fn(async () => {}), @@ -30,7 +31,7 @@ function setup() { nextChatActionAt: vi.fn(async (): Promise => null), nextVerificationMessageAt: vi.fn(async (): Promise => null), }; - const runs = [service.processPendingProviderEffects, service.processPendingGitHubWebhookIngress, service.processPendingSlackSessionSyncs, service.processPendingSlackBoardMessages, service.processPendingSlackTaskStarts, + const runs = [service.processFailedChatRunRetries, service.processPendingProviderEffects, service.processPendingGitHubWebhookIngress, service.processPendingSlackSessionSyncs, service.processPendingSlackBoardMessages, service.processPendingSlackTaskStarts, service.processPendingReceiptReactions, service.processPendingSlackSessionStops, service.processPendingVerificationMessages]; return { owner, service, runs, coordinator, enabled, onError, start: () => registerChatActionWork(coordinator, service, enabled) }; } @@ -40,7 +41,7 @@ describe("chat action scheduling", () => { await s.start().ready; await vi.advanceTimersByTimeAsync(3_600_000); for (const run of s.runs) expect(run).toHaveBeenCalledTimes(1); - expect(s.service.nextChatActionAt).toHaveBeenCalledTimes(7); + expect(s.service.nextChatActionAt).toHaveBeenCalledTimes(8); expect(s.service.nextVerificationMessageAt).toHaveBeenCalledTimes(1); expect(s.coordinator.nextWakeAt()).toBeNull(); expect(vi.getTimerCount()).toBe(0); diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index a550acedf8b..d8c86374466 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -1,3 +1,4 @@ +import { idleWorkSnapshot } from "../services/task-admission.js"; import * as activityLogModule from "../services/activity-log.js"; import { chatSlackManagerGrants, chatSlackRegistrations, toolOauthStates } from "@paperclipai/db"; import type { PaperclipCloudConnector, SealedConnectorCredentials } from "../services/paperclip-cloud-connector.js"; @@ -33026,6 +33027,26 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { await set("delivery_unknown"); expect(await f.service.nextChatActionAt(kind)).toBeNull(); expect(await due()).toBe(false); + } else if (kind === "failed_run_retry") { + expect(await due()).toBe(false); + await set("issued"); + expect(await due()).toBe(true); + await set("issued", { retryAt: later.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(later.getTime()); + expect(await due()).toBe(false); + await set("processing"); + expect(await f.service.nextChatActionAt(kind)).toBe(at.getTime() + 60_000); + expect(await due()).toBe(false); + // Both the claim lease and retry time must expire before replay. + await set("processing", { retryAt: later.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(later.getTime()); + await set("processing", { retryAt: at.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBe(at.getTime() + 60_000); + for (const status of ["failed", "cancelled", "processed"]) { + await set(status, { retryable: true, retryAt: at.toISOString() }); + expect(await f.service.nextChatActionAt(kind)).toBeNull(); + expect(await due()).toBe(false); + } } else if (kind === "slack_session_sync") { expect(await due()).toBe(true); await set("received", { retryAt: later.toISOString() }); @@ -59779,6 +59800,81 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { }, ); + it.each(["commit", "retry", "startup", "stale_claim", "rollback"] as const)( + "drains failed chat retries from durable work and becomes idle: %s", + async (mode) => { + const context = await failedChatRetryFixture("telegram"); + const onError = vi.fn(); + const coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => true, onError }); + const input = { companyId: context.fixture.companyId, issueId: context.issue.id, + agentId: context.fixture.assignedAgentId, failedRunId: context.runId, initiatedByUserId: "owner-user" }; + const stage = () => db.transaction(tx => context.service.prepareFailedChatRunRetry(tx, input)); + context.wakeup.mockClear(); + try { + let actionId: string; + if (mode === "startup" || mode === "stale_claim") { + ({ actionId } = await stage()); + if (mode === "stale_claim") await db.update(chatActions).set({ status: "processing", + updatedAt: new Date(Date.now() - 120_000) }).where(eq(chatActions.id, actionId)); + await registerChatActionWork(coordinator, context.service, () => true).ready; + } else { + await registerChatActionWork(coordinator, context.service, () => true).ready; + if (mode === "rollback") { + await expect(db.transaction(async tx => { + await context.service.prepareFailedChatRunRetry(tx, input); + throw new Error("rollback retry intent"); + })).rejects.toThrow("rollback retry intent"); + await vi.waitFor(() => { + expect(coordinator.nextWakeAt()).toBeNull(); + expect(idleWorkSnapshot().active).toBe(0); + }, { timeout: 10_000 }); + expect(context.wakeup).not.toHaveBeenCalled(); + } + if (mode === "retry") { + let failedAt = 0; + context.wakeup.mockImplementationOnce(async () => { + failedAt = Date.now(); + throw new Error("temporary admission outage"); + }).mockImplementationOnce(async () => { + expect(Date.now() - failedAt).toBeGreaterThanOrEqual(250); + return { accepted: true }; + }); + } + ({ actionId } = await db.transaction(async tx => { + const staged = await tx.transaction(nested => context.service.prepareFailedChatRunRetry(nested, input)); + // A nested savepoint must not wake the worker before the outer commit. + await new Promise(resolve => setTimeout(resolve, 25)); + expect(context.wakeup).not.toHaveBeenCalled(); + return staged; + })); + } + await vi.waitFor(async () => { + const [action] = await db.select().from(chatActions).where(eq(chatActions.id, actionId)); + expect(action?.status).toBe("processed"); + expect(action?.result.attemptCount).toBe(mode === "retry" ? 2 : 1); + expect(coordinator.nextWakeAt()).toBeNull(); + }, { timeout: 10_000 }); + expect(context.wakeup).toHaveBeenCalledTimes(mode === "retry" ? 2 : 1); + const receipts = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, actionId)); + expect(receipts).toHaveLength(1); + expect(receipts[0]?.payload).toMatchObject({ retryOfRunId: context.runId, + wakeCommentIds: [context.action.payload.commentId] }); + // A running worker has no scheduled deadline. Wait for its final scan + // and any commit-driven follow-up before measuring the quiet period. + await vi.waitFor(() => { + expect(coordinator.nextWakeAt()).toBeNull(); + expect(idleWorkSnapshot().active).toBe(0); + }, { timeout: 10_000 }); + const scans = vi.spyOn(db, "select"); + try { + await new Promise(resolve => setTimeout(resolve, 1200)); + expect(scans).not.toHaveBeenCalled(); + } finally { scans.mockRestore(); } + expect(onError).not.toHaveBeenCalled(); + } finally { await coordinator.stop(); await context.service.shutdown(); } + }, + ); + it.each(["slack", "telegram"] as const)( "retries the exact failed %s chat input with a new idempotent admission", async (provider) => { diff --git a/server/src/services/chat-action-work.ts b/server/src/services/chat-action-work.ts index f794cc6a0c2..6d1847526ed 100644 --- a/server/src/services/chat-action-work.ts +++ b/server/src/services/chat-action-work.ts @@ -8,6 +8,7 @@ import { registerChatQueueWork } from "./chat-delivery-work.js"; export const SLACK_COMMAND_POST_STALE_MS = 60_000; export const SLACK_COMMAND_EXPLICIT_RETRY_STALE_MS = 5 * 60_000; export const SLACK_COMMAND_ADMISSION_STALE_MS = 60_000; +export const FAILED_RUN_RETRY_STALE_MS = 60_000; export const PROVIDER_EFFECT_STALE_MS = 60_000; /** The dispatch predicate and scheduler use the same eligibility and deadlines. @@ -15,6 +16,9 @@ export const PROVIDER_EFFECT_STALE_MS = 60_000; export function chatActionDeadline(kind: ScheduledChatAction) { const updated = sql`extract(epoch from ${chatActions.updatedAt}) * 1000`; const retry = sql`coalesce(extract(epoch from (${chatActions.result}->>'retryAt')::timestamptz) * 1000, 0)`; + if (kind === "failed_run_retry") return sql`case + when ${chatActions.status} = 'issued' then ${retry} + when ${chatActions.status} = 'processing' then greatest(${updated} + ${FAILED_RUN_RETRY_STALE_MS}, ${retry}) end`; if (kind === "slack_board_message") return sql`case when ${chatActions.status} = 'received' and ${chatEndpoints.status} not in ('paused', 'attention') then 0::numeric end`; if (kind === "slash_task_start") return sql`case @@ -36,14 +40,16 @@ export function chatActionDeadline(kind: ScheduledChatAction) { function pendingChatAction(kind: ScheduledChatAction) { // Filter history before the endpoint join and aggregate. Failed actions // only remain live when the saved result permits another attempt. - const status = kind === "slack_board_message" - ? eq(chatActions.status, "received") - : kind === "slash_task_start" - ? inArray(chatActions.status, ["queued", "provider_confirmed", "admitting", "resolving", "validating"]) - : kind === "slack_session_sync" - ? inArray(chatActions.status, ["received", "processing"]) - : or(inArray(chatActions.status, ["received", "processing"]), - and(eq(chatActions.status, "failed"), sql`${chatActions.result}->>'retryable' = 'true'`)); + const status = kind === "failed_run_retry" + ? inArray(chatActions.status, ["issued", "processing"]) + : kind === "slack_board_message" + ? eq(chatActions.status, "received") + : kind === "slash_task_start" + ? inArray(chatActions.status, ["queued", "provider_confirmed", "admitting", "resolving", "validating"]) + : kind === "slack_session_sync" + ? inArray(chatActions.status, ["received", "processing"]) + : or(inArray(chatActions.status, ["received", "processing"]), + and(eq(chatActions.status, "failed"), sql`${chatActions.result}->>'retryable' = 'true'`)); return and(eq(chatActions.kind, kind), status); } @@ -60,6 +66,7 @@ export async function nextChatActionAt(db: Db, kind: ScheduledChatAction): Promi } type ChatActionService = { + processFailedChatRunRetries(): Promise; processPendingProviderEffects(): Promise; processPendingGitHubWebhookIngress(): Promise; processPendingSlackSessionSyncs(): Promise; @@ -78,6 +85,7 @@ export function registerChatActionWork( canRun: () => boolean, ) { const actions = [ + { kind: "failed_run_retry", run: service.processFailedChatRunRetries }, { kind: "provider_effect", run: service.processPendingProviderEffects }, { kind: "github_webhook_ingress", run: service.processPendingGitHubWebhookIngress }, { kind: "slack_session_sync", run: service.processPendingSlackSessionSyncs }, diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index 2bd74bbb9d6..7716efe06aa 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -5729,7 +5729,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { lifecycleRuntimeFence, isShuttingDown: () => shuttingDown, wakeup: (...args) => options.heartbeat.wakeup(...args), - staleProcessingMs: DELIVERY_PROCESSING_STALE_MS, }); const { stageTeamsBoardFileIntent, deriveTeamsFileTransferAuthority } = createTeamsFileAuthority({ runtimeGeneration, credentialFingerprint, runtime, runtimeContexts, localRuntimeEpoch, runtimeVersions, lockCurrentPrincipalAuthorization, lifecycleRuntimeFence, assertFailedRetryReceipt, inboundWakeScope, failedChatRetrySource, isExplicitOperatorPublication, retryCommentIds }); async function authorizeRetainedChatSourcePublication( @@ -15429,7 +15428,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { async function processPendingChatMaintenance(limit = 25) { return Promise.allSettled([ processPendingTelegramMaintenance(limit), - processFailedChatRunRetries(limit), ]); } @@ -15440,7 +15438,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { // so a crashed processing claim is quarantined before the delivery worker // could otherwise replay it. const actionRecovery = onlyDeliveryId || !maintenance ? null : Promise.allSettled([ - processPendingChatMaintenance(limit), processPendingGitHubWebhookIngress(limit), + processPendingChatMaintenance(limit), processFailedChatRunRetries(limit), + processPendingGitHubWebhookIngress(limit), processPendingProviderEffects(limit), processPendingSlackBoardMessages(limit), processPendingSlackTaskStarts(limit), processPendingReceiptReactions(limit), processPendingSlackSessionStops(limit), slackRegistration.processPendingVerificationMessages(limit), diff --git a/server/src/services/chat-channels/failed-run-retry.ts b/server/src/services/chat-channels/failed-run-retry.ts index c0b8d43988f..ae041ef9251 100644 --- a/server/src/services/chat-channels/failed-run-retry.ts +++ b/server/src/services/chat-channels/failed-run-retry.ts @@ -1,5 +1,6 @@ import { continuingTeamsFilePublication, type ContinuingTeamsFileTransfer } from "./microsoft-teams/file-transfer-source.js"; -import { notifyChatPublicationWork } from "../chat-work-notifications.js"; +import { notifyChatActionWork, notifyChatPublicationWork } from "../chat-work-notifications.js"; +import { FAILED_RUN_RETRY_STALE_MS, dueChatAction } from "../chat-action-work.js"; import { createHash, @@ -13,7 +14,6 @@ import { isNotNull, isNull, like, - lte, ne, notInArray, or, @@ -104,7 +104,6 @@ type ChatFailedRunRetryOptions = { lifecycleRuntimeFence: (delivery: DeliveryRow) => RetryRuntimeFence | null; isShuttingDown: () => boolean; wakeup: IssueAssignmentWakeupDeps["wakeup"]; - staleProcessingMs: number; }; export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOptions) { @@ -115,7 +114,6 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti lifecycleRuntimeFence, isShuttingDown, wakeup, - staleProcessingMs: DELIVERY_PROCESSING_STALE_MS, } = options; @@ -1265,6 +1263,7 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti .limit(1); if (busy || !input.initiatedByUserId) throw failedChatRetryDenied(); const now = new Date(); + await notifyChatActionWork(tx, "failed_run_retry"); const [intent] = await tx .insert(chatActions) .values({ @@ -1508,36 +1507,42 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti !["issued", "processing"].includes(candidate.status) || (candidate.status === "processing" && candidate.updatedAt.getTime() > - now.getTime() - DELIVERY_PROCESSING_STALE_MS) || + now.getTime() - FAILED_RUN_RETRY_STALE_MS) || (typeof candidate.result?.retryAt === "string" && Date.parse(candidate.result.retryAt) > now.getTime()) ) return failedChatRetryReceipt(candidate); - const [claimed] = await db - .update(chatActions) - .set({ status: "processing", updatedAt: now }) - .where( - and( - eq(chatActions.id, candidate.id), - eq(chatActions.status, candidate.status), - eq(chatActions.updatedAt, candidate.updatedAt), - ), - ) - .returning(); - if (!claimed) return failedChatRetryReceipt(candidate); - const attemptCount = Number(candidate.result?.attemptCount ?? 0) + 1; - const settle = async (status: string, result: Record) => { - const [current] = await db + const [claimed] = await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "failed_run_retry"); + return tx .update(chatActions) - .set({ status, result, updatedAt: new Date() }) + .set({ status: "processing", updatedAt: now }) .where( and( - eq(chatActions.id, claimed.id), - eq(chatActions.status, "processing"), - eq(chatActions.updatedAt, now), + eq(chatActions.id, candidate.id), + eq(chatActions.status, candidate.status), + eq(chatActions.updatedAt, candidate.updatedAt), ), ) .returning(); + }); + if (!claimed) return failedChatRetryReceipt(candidate); + const attemptCount = Number(candidate.result?.attemptCount ?? 0) + 1; + const settle = async (status: string, result: Record) => { + const [current] = await db.transaction(async (tx) => { + await notifyChatActionWork(tx, "failed_run_retry"); + return tx + .update(chatActions) + .set({ status, result, updatedAt: new Date() }) + .where( + and( + eq(chatActions.id, claimed.id), + eq(chatActions.status, "processing"), + eq(chatActions.updatedAt, now), + ), + ) + .returning(); + }); return failedChatRetryReceipt(current ?? claimed); }; try { @@ -1669,26 +1674,10 @@ export function createChatFailedRunRetry(db: Db, options: ChatFailedRunRetryOpti async function processFailedChatRunRetries(limit = 20) { if (isShuttingDown()) return 0; - const now = new Date(); const candidates = await db .select({ id: chatActions.id }) .from(chatActions) - .where( - and( - eq(chatActions.kind, "failed_run_retry"), - or( - eq(chatActions.status, "issued"), - and( - eq(chatActions.status, "processing"), - lte( - chatActions.updatedAt, - new Date(now.getTime() - DELIVERY_PROCESSING_STALE_MS), - ), - ), - ), - sql`(${chatActions.result}->>'retryAt' is null or (${chatActions.result}->>'retryAt')::timestamptz <= ${now.toISOString()}::timestamptz)`, - ), - ) + .where(dueChatAction("failed_run_retry")) .orderBy(asc(chatActions.createdAt), asc(chatActions.id)) .limit(Math.max(1, Math.min(50, limit))); for (const candidate of candidates) diff --git a/server/src/services/chat-work-notifications.ts b/server/src/services/chat-work-notifications.ts index f76ac13c72e..16e2ac75135 100644 --- a/server/src/services/chat-work-notifications.ts +++ b/server/src/services/chat-work-notifications.ts @@ -13,6 +13,7 @@ export async function notifyChatPublicationWork(tx: object): Promise { export const CHAT_ACTION_QUEUES = { provider_effect: DELIVERY_QUEUES.chatProviderEffects, github_webhook_ingress: DELIVERY_QUEUES.chatGitHubIngress, + failed_run_retry: DELIVERY_QUEUES.chatFailedRunRetries, slack_session_sync: DELIVERY_QUEUES.chatSessionSyncs, slack_board_message: DELIVERY_QUEUES.chatBoardMessages, slash_task_start: DELIVERY_QUEUES.chatTaskStarts, diff --git a/server/src/services/delivery-work-notifications.ts b/server/src/services/delivery-work-notifications.ts index fdee30932f8..ab4ba5fe257 100644 --- a/server/src/services/delivery-work-notifications.ts +++ b/server/src/services/delivery-work-notifications.ts @@ -8,6 +8,7 @@ export const DELIVERY_QUEUES = { chatSessionStops: "chat-session-stops", chatProviderEffects: "chat-provider-effects", chatGitHubIngress: "chat-github-ingress", + chatFailedRunRetries: "chat-failed-run-retries", chatSessionSyncs: "chat-session-syncs", chatVerification: "chat-verification", chatInbound: "chat-inbound", From 6c198d616e01510e09f719c2cad9a55ce92bcab3 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Mon, 12 Oct 2026 20:34:28 -0700 Subject: [PATCH 17/37] fix(issues): preserve human handoffs from the active agent run (#16082) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Tasks have one owner, and the server records assignment changes. > - An agent can finish its work by handing the task to a human for review. > - The assignment route stops the active run before it saves that handoff. > - When the caller is that run, a slow workspace restore can exceed the stop timeout and prevent the assignment from saving. > - Recovery can then mark the task Blocked while its comment says the handoff succeeded. > - This pull request lets the authenticated legacy run save a human handoff and finish its turn normally. > - Other actors and transfers to executable agents keep the verified stop requirement. ## Linked Issues or Issue Description Refs #13730. Related: #13782, #14700, and #15094. **What happened?** An agent posted its review handoff, then sent `PATCH /api/issues/{id}` to assign the task to its human creator with status `in_review`. The route cancelled that same run before saving the assignment. Workspace restore took 66 seconds. The stop limit is 60 seconds. The assignment never committed. Recovery marked the task Blocked, and it stayed assigned to the agent. **Expected behavior** The authenticated active legacy run can hand its own task to a human and finish normally. The human assignment and requested review status must save without waiting for workspace restore. Native runs, board reassignment, agent API keys, and transfers to another agent still require a verified stop. **Steps to reproduce** 1. Run a legacy agent on its assigned task with a signed run token. 2. Send a task PATCH from that run with a human `assigneeUserId`, a null `assigneeAgentId`, and status `in_review`. 3. Make stopping the sandbox take more than 60 seconds. Before this fix, the handoff fails before the task update. With this fix, the update saves and the run finishes normally. **Paperclip version or commit** Reproduced in hosted execution. The patch is based on master at `56a0bf0d492`. **Deployment mode** Hosted authenticated server with a legacy agent in a remote sandbox. The same route serves local deployments. PR #13782 proposes a broader exemption for all self-handoffs. This focused change limits the exemption to human recipients and legacy runs. It checks the exact active run, agent, company, and task, and it retains the stop requirement before an executable successor can take ownership. PRs #14700 and #15094 address later wake and recovery behavior after a reassignment stop. ## What Changed - Skip the reassignment cancellation only for an authenticated legacy run handing its own task to a human. - Reject later mutations from the outgoing run against its human-owned task, both in route authorization and under the update transaction locks. - Keep assignment permission checks, goal control, and queued human response protection. - Add route regressions for slow stops, active-run lookup, actor and runtime boundaries, recipient types, permission denials, and follow-up edits, completion, and reassignment. - Update the real HTTP low-trust handoff test to prove the run stays alive, its late edit fails, and the human wake receipt remains invalidated. - Add Postgres tests for saved assignment, former-owner rejection, and review state after the outgoing run succeeds or fails. - Document the handoff rule and update the task feature and agent access maps. ## Verification - 328 relevant tests passed across the route, issue service, low-trust HTTP, and legacy recovery suites. The route suite passed all 160 tests, and all 13 low-trust HTTP tests passed after updating the obsolete cancellation expectation. - Removing the production guard makes both slow-shutdown handoff regressions fail with HTTP 500. - `pnpm -r typecheck` passed. - `pnpm build` passed. - `node scripts/check-maps.mjs` passed. - `git diff --check` passed. - All 54 current-head checks passed; 2 optional Storybook checks were skipped. The CI lanes cover the full Vitest suite, typecheck/build, Runner checks, and product E2E checks. - Apex reviewed `c68fbfb356c3d80eb7136babb49940709a3c3459` at 5/5 with no findings; both earlier review threads are resolved. - The complete test lanes passed in CI. The duplicate local full `pnpm test:run` was stopped after CI completed; it did not finish locally. All 328 focused local tests and full local typecheck/build passed. ## Risks - The outgoing legacy turn stays alive after the human takes ownership. Route and transactional ownership checks reject its later mutations of the human-owned task. Read and comment access follows normal policy. Goal control still runs before the handoff. - This exemption does not apply to native runs or another agent as recipient. Those paths still stop the old execution before assignment. - This fix prevents future failed handoffs. It does not infer or replay old assignment requests. - There is no schema change or API shape change. ## Model Used OpenAI GPT-6 through Codex, with reasoning, repository tools, and code execution. The session does not expose a more specific runtime model ID or context window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- doc/execution-semantics.md | 11 ++ maps/agent-access-map/tasks.md | 12 +- maps/feature-map/tasks.md | 7 + ...ue-agent-mutation-ownership-routes.test.ts | 147 ++++++++++++++++++ server/src/__tests__/issues-service.test.ts | 65 ++++++++ .../low-trust-red-team-routes.test.ts | 15 +- server/src/agent-run-cancellation.ts | 5 +- server/src/legacy-run-task-ownership.ts | 24 +++ server/src/routes/issues.ts | 19 ++- server/src/services/issues.ts | 4 +- 10 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 server/src/legacy-run-task-ownership.ts diff --git a/doc/execution-semantics.md b/doc/execution-semantics.md index f5632ecb178..238bf4b8b8a 100644 --- a/doc/execution-semantics.md +++ b/doc/execution-semantics.md @@ -29,6 +29,17 @@ An issue has at most one assignee. This is a hard invariant. Paperclip is single-assignee by design. +A legacy agent run can hand its own task to a human without cancelling itself. +The REST request must use the run's signed token and match the current agent, +company, task, and active run. The assignment and requested status persist while +the current turn finishes normally. This avoids losing the handoff when workspace +restoration takes longer than the stop timeout. The former agent no longer owns +the task. Its later task mutations are rejected, including under the update +transaction's task and run locks. Read and comment access follows normal policy. + +Board-initiated reassignments, agent API keys, native executions, and handoffs to +another agent still require the outgoing execution to stop before ownership changes. + ## 3. Status Semantics Paperclip issue statuses are not just UI labels. They imply different expectations about ownership and execution. diff --git a/maps/agent-access-map/tasks.md b/maps/agent-access-map/tasks.md index d0ccdbc978c..b3272d30a74 100644 --- a/maps/agent-access-map/tasks.md +++ b/maps/agent-access-map/tasks.md @@ -71,7 +71,12 @@ Agent access: `partial` — Native set_task_title/reassign_task/set_dependencies for permitted non-lifecycle fields; REST. Policy: Company/task visibility, assignment/change policy, active-run checkout ownership -and lifecycle rules. +and lifecycle rules. A legacy run can assign its own task to a human without +cancelling itself only with its signed run token and matching active run, agent, +company and task. The outgoing run cannot mutate its human-owned task afterward, +including through a late update already admitted before the handoff. Agent keys, +native runs, board reassignment and transfers to +another agent retain verified-stop requirements. Evidence: `source-only` — [implementation](../../server/src/routes/issues.ts) at the reviewed commit above. @@ -79,7 +84,10 @@ reviewed commit above. Verification: `not-run` — exercise a listed permitted operation with actual agent credentials/run context; inspect its result and applicable persistence/attribution. Assert the relevant denied actor, owner, company or mode from Policy. Review the -remaining operations before qualifying the whole capability. +remaining operations before qualifying the whole capability. Focused route +regressions in [ownership tests](../../server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts) +cover a human handoff and stop enforcement for other actors, runtimes and targets; +they use mocked services and do not claim live execution proof. Gaps: Native fallback blocks status/assignment/dependency policy fields. Dedicated tools differ in scope and supported fields. diff --git a/maps/feature-map/tasks.md b/maps/feature-map/tasks.md index 19eb631af3c..6ed9fd7f353 100644 --- a/maps/feature-map/tasks.md +++ b/maps/feature-map/tasks.md @@ -55,6 +55,13 @@ Manual: Filter by status and assignee, change grouping/sort, open a task, and re Automated: [task detail](../../ui/src/pages/IssueDetail.test.tsx) covers component behavior, while [issue service](../../server/src/__tests__/issues-service.test.ts) covers server contracts. +Manual: For a disposable task owned by a legacy agent, have its current run +assign the task to a human with status In review. Verify that assignment and +status persist, that the current run finishes without `issue_reassigned`, and +that slow workspace restoration does not turn the task Blocked. Try a later edit +from the outgoing run and verify rejection. Repeat a board +reassignment and confirm that the outgoing run still stops. + Manual: Add a comment and edit authorized properties. Reload and verify history, actor, and values. Complete a disposable task and inspect its deliverable; reopen through an available action and confirm the next execution follows current policy. ## Gotchas diff --git a/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts b/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts index 5054ca63d49..6aa18723cba 100644 --- a/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts +++ b/server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts @@ -325,6 +325,7 @@ function createRunContextDb( runAgentOrRows: string | Record[] = ownerAgentId, runId: string = ownerRunId, chatBindings: Array<{ id: string; companyId: string; issueId: string; state: string }> = [], + emptyWakeQueue = false, ) { const chatBindingQueries: ReturnType[] = []; const runRows = Array.isArray(runAgentOrRows) @@ -382,6 +383,9 @@ function createRunContextDb( transaction: async (callback: (tx: typeof dbStub) => Promise) => callback(dbStub), select: vi.fn((selection: Record = {}) => ({ from: vi.fn((table: Parameters[0]) => { + if (emptyWakeQueue && getTableName(table) === "agent_wakeup_requests") { + return buildQuery({}, false, true); + } if (getTableName(table) === "issue_thread_interactions") { return { where: vi.fn(() => ({ limit: vi.fn(async () => []) })) }; } @@ -2688,6 +2692,149 @@ describe("agent issue mutation checkout ownership", () => { expect(mockIssueService.update).not.toHaveBeenCalled(); }); + describe("human self-handoff", () => { + const runningRun = { + id: ownerRunId, + companyId, + agentId: ownerAgentId, + runtimeMode: "legacy", + status: "running", + contextSnapshot: { issueId }, + }; + const handoff = { + assigneeAgentId: null, + assigneeUserId: "board-user", + status: "in_review", + }; + const handoffApp = (actor: Record) => createApp(actor, + createRunContextDb({}, ownerAgentId, ownerRunId, [], true)); + + beforeEach(() => { + mockIssueService.getById.mockResolvedValue(makeIssue({ + status: "in_review", + executionRunId: ownerRunId, + })); + mockHeartbeatService.getRun.mockResolvedValue(runningRun); + mockHeartbeatService.cancelRun.mockResolvedValue({ ...runningRun, status: "cancelled" }); + mockAgentService.resolveByReference.mockImplementation(async (_companyId: string, id: string) => ({ + ambiguous: false, + agent: await mockAgentService.getById(id), + })); + }); + + it.each(["in_progress", "in_review"])("persists a human handoff from %s without waiting for its own slow shutdown", async status => { + mockIssueService.getById.mockResolvedValue(makeIssue({ status, executionRunId: ownerRunId })); + mockHeartbeatService.cancelRun.mockRejectedValue(new Error("Execution is still stopping; termination has not been verified.")); + + const res = await request(handoffApp({ ...ownerActor(), source: "agent_jwt" })) + .patch(`/api/issues/${issueId}`).send(handoff); + + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(res.body).toMatchObject(handoff); + expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); + expect(mockIssueService.update.mock.calls[0]?.[1]).toMatchObject({ + ...handoff, + actorRunId: ownerRunId, + actorRunStopId: null, + }); + expect(mockLogActivity.mock.calls.find(call => call[1].action === "issue.updated")?.[1]).toMatchObject({ + action: "issue.updated", + runId: ownerRunId, + details: handoff, + }); + expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + }); + + it("recognizes the caller when the active run is resolved without an issue execution lock", async () => { + mockIssueService.getById.mockResolvedValue(makeIssue({ status: "in_review" })); + mockHeartbeatService.getActiveRunForAgent.mockResolvedValue(runningRun); + const res = await request(handoffApp({ ...ownerActor(), source: "agent_jwt" })) + .patch(`/api/issues/${issueId}`).send(handoff); + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(res.body).toMatchObject(handoff); + expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); + }); + + it.each(["edit", "complete", "reclaim"])("rejects the outgoing run's later %s after handing the task to a human", async action => { + const app = handoffApp({ ...ownerActor(), source: "agent_jwt" }); + const handedOff = await request(app).patch(`/api/issues/${issueId}`).send(handoff); + expect(handedOff.status, JSON.stringify(handedOff.body)).toBe(200); + mockIssueService.getById.mockResolvedValue(makeIssue(handoff)); + mockIssueService.update.mockClear(); + + const res = await request(app).patch(`/api/issues/${issueId}`).send( + action === "edit" ? { title: "Late outgoing edit" } : + action === "complete" ? { status: "done" } : + { assigneeAgentId: ownerAgentId, assigneeUserId: null }, + ); + + expect(res.status, JSON.stringify(res.body)).toBe(403); + expect(res.body.details).toMatchObject({ code: "issue_handed_off" }); + expect(mockIssueService.update).not.toHaveBeenCalled(); + expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); + }); + + it("keeps a queued human response with its current agent", async () => { + const res = await request(createApp({ ...ownerActor(), source: "agent_jwt" })) + .patch(`/api/issues/${issueId}`).send(handoff); + expect(res.status, JSON.stringify(res.body)).toBe(409); + expect(res.body.code).toBe("interaction_response_queued"); + expect(mockIssueService.update).not.toHaveBeenCalled(); + expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); + }); + + it("retains assignment permission checks for a different human recipient", async () => { + mockAccessService.decide.mockImplementation(async (input: { action: string }) => ({ + allowed: input.action !== "tasks:assign", + action: input.action, + reason: "deny_policy_restricted", + explanation: "Assignment requires approval.", + })); + const res = await request(handoffApp({ ...ownerActor(), source: "agent_jwt" })) + .patch(`/api/issues/${issueId}`).send({ ...handoff, assigneeUserId: "another-user" }); + expect(res.status, JSON.stringify(res.body)).toBe(403); + expect(mockIssueService.update).not.toHaveBeenCalled(); + expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); + }); + + it.each(["board", "agent_key", "other_run", "native", "other_issue", "other_agent", "other_company"])( + "still requires a verified stop for %s reassignment", async scenario => { + let actor: Record = { ...ownerActor(), source: "agent_jwt" }; + if (scenario === "board") actor = boardActor(); + if (scenario === "agent_key") actor = ownerActor(); + const run = { + ...runningRun, + ...(scenario === "other_run" ? { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" } : {}), + ...(scenario === "native" ? { runtimeMode: "native" } : {}), + ...(scenario === "other_issue" ? { contextSnapshot: { issueId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" } } : {}), + ...(scenario === "other_agent" ? { agentId: peerAgentId } : {}), + ...(scenario === "other_company" ? { companyId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" } : {}), + }; + mockHeartbeatService.getRun.mockResolvedValue(run); + mockHeartbeatService.cancelRun.mockRejectedValue(new Error("Execution is still stopping; termination has not been verified.")); + + const res = await request(handoffApp(actor)).patch(`/api/issues/${issueId}`).send(handoff); + + expect(res.status, JSON.stringify(res.body)).toBe(500); + expect(mockHeartbeatService.cancelRun).toHaveBeenCalledWith(run.id, "Cancelled before issue reassignment", + expect.objectContaining({ errorCode: "issue_reassigned" })); + expect(mockIssueService.update).not.toHaveBeenCalled(); + }, + ); + + it.each(["agent", "unassigned"])("still stops the outgoing run before an %s handoff", async target => { + const res = await request(handoffApp({ ...ownerActor(), source: "agent_jwt" })) + .patch(`/api/issues/${issueId}`).send({ + assigneeAgentId: target === "agent" ? peerAgentId : null, + assigneeUserId: null, + }); + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(mockHeartbeatService.cancelRun).toHaveBeenCalledTimes(1); + expect(mockHeartbeatService.cancelRun.mock.invocationCallOrder[0]) + .toBeLessThan(mockIssueService.update.mock.invocationCallOrder[0]!); + }); + }); + describe("task watchdog scope grants", () => { const watchdogRunId = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaab"; const watchdogReportIssueId = "cccccccc-cccc-4ccc-8ccc-cccccccccccd"; diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 4475e075122..5f3f8c58ae7 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -21,6 +21,7 @@ import { issueAccessGrants, issuePlanDecompositions, issueReadStates, + issueRecoveryActions, issueRelations, issueThreadInteractions, issueWorkProducts, @@ -46,6 +47,8 @@ import { readIssueCommentRunLogText, } from "../services/issues.ts"; import { getRunLogStore } from "../services/run-log-store.js"; +import { terminalizeLegacyExecution } from "../services/legacy-execution-recovery.js"; +import { settleUnrecoverableExecutions } from "../services/execution-recovery-resolution.js"; import { WORKSPACE_WORKTREE_REQUIRES_PROJECT_CODE, WORKSPACE_WORKTREE_REQUIRES_PROJECT_MESSAGE, @@ -7048,6 +7051,7 @@ describeEmbeddedPostgres("issueService.assertCheckoutOwner stale checkout adopti await db.delete(goals); await db.delete(agents); await db.delete(instanceSettings); + await db.delete(companyMemberships); await db.delete(companies); }); @@ -7136,6 +7140,67 @@ describeEmbeddedPostgres("issueService.assertCheckoutOwner stale checkout adopti return { issueId, assigneeAgentId, actorAgentId, staleRunId, actorRunId }; } + it.each(["succeeded", "failed"])("preserves a human handoff when the outgoing legacy run later %s", async status => { + const seeded = await seedOwnershipIssue({ checkoutStatus: "failed" }); + const original = await svc.getById(seeded.issueId); + await db.insert(companyMemberships).values({ + companyId: original!.companyId, + principalType: "user", + principalId: "handoff-reviewer", + status: "active", + }); + await db.update(heartbeatRuns).set({ + contextSnapshot: { issueId: seeded.issueId }, + }).where(eq(heartbeatRuns.id, seeded.actorRunId)); + await svc.assertCheckoutOwner(seeded.issueId, seeded.actorAgentId, seeded.actorRunId); + + const handoff = await svc.update(seeded.issueId, { + assigneeAgentId: null, + assigneeUserId: "handoff-reviewer", + status: "in_review", + actorAgentId: seeded.actorAgentId, + actorRunId: seeded.actorRunId, + }); + expect(handoff).toMatchObject({ + assigneeAgentId: null, + assigneeUserId: "handoff-reviewer", + status: "in_review", + checkoutRunId: null, + executionRunId: null, + }); + const [run] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, seeded.actorRunId)); + expect(run!.status).toBe("running"); + await expect(svc.assertCheckoutOwner(seeded.issueId, seeded.actorAgentId, seeded.actorRunId)) + .rejects.toMatchObject({ status: 409 }); + await expect(svc.update(seeded.issueId, { + title: "Late outgoing edit", + actorAgentId: seeded.actorAgentId, + actorRunId: seeded.actorRunId, + })).rejects.toMatchObject({ status: 403, details: { code: "issue_handed_off" } }); + + await terminalizeLegacyExecution({ + db, + run: run!, + status, + fromStatuses: ["running"], + reconcileIfNeeded: true, + patch: { finishedAt: new Date(), ...(status === "failed" ? { errorCode: "process_lost" } : {}) }, + }); + await settleUnrecoverableExecutions(db); + await expect(svc.update(seeded.issueId, { + status: "done", + actorAgentId: seeded.actorAgentId, + actorRunId: seeded.actorRunId, + })).rejects.toMatchObject({ status: 403, details: { code: "issue_handed_off" } }); + expect(await svc.getById(seeded.issueId)).toMatchObject({ + status: "in_review", + assigneeAgentId: null, + assigneeUserId: "handoff-reviewer", + }); + expect(await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, seeded.issueId))) + .toHaveLength(0); + }); + it("lets the current assignee adopt a stale terminal checkout owner", async () => { const seeded = await seedOwnershipIssue({ checkoutStatus: "failed" }); diff --git a/server/src/__tests__/low-trust-red-team-routes.test.ts b/server/src/__tests__/low-trust-red-team-routes.test.ts index bafcdfce5ac..8c952f63708 100644 --- a/server/src/__tests__/low-trust-red-team-routes.test.ts +++ b/server/src/__tests__/low-trust-red-team-routes.test.ts @@ -977,14 +977,19 @@ describeEmbeddedPostgres( const handoff = await request(directedAgent).patch(`/api/issues/${created.body.id}`) .send({ status: "in_review", assigneeAgentId: null, assigneeUserId: "board-user", comment: "Ready for human review" }); expect(handoff.status, JSON.stringify(handoff.body)).toBe(200); - const [stopped] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, directRun.id)); - expect(stopped).toMatchObject({ status: "cancelled", errorCode: "issue_reassigned" }); + expect(handoff.body).toMatchObject({ status: "in_review", assigneeAgentId: null, assigneeUserId: "board-user" }); + const [outgoing] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, directRun.id)); + expect(outgoing).toMatchObject({ status: "running", errorCode: null }); + const lateEdit = await request(directedAgent).patch(`/api/issues/${created.body.id}`) + .send({ description: "Late outgoing edit" }); + expect([403, 404]).toContain(lateEdit.status); const returned = await request(board).patch(`/api/issues/${created.body.id}`) .send({ status: "backlog", assigneeAgentId: fixture.agents.lowTrust.id, assigneeUserId: null }); expect(returned.status, JSON.stringify(returned.body)).toBe(200); - // Reassignment back does not revive the cancelled run's human request. - const staleRead = await request(directedAgent).get(`/api/issues/${created.body.id}`); - expect([403, 404]).toContain(staleRead.status); + const [humanRequest] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, wake.id)); + expect(humanRequest.status).toBe("cancelled"); + const reassignedRead = await request(directedAgent).get(`/api/issues/${created.body.id}`); + expect(reassignedRead.status, JSON.stringify(reassignedRead.body)).toBe(200); }); diff --git a/server/src/agent-run-cancellation.ts b/server/src/agent-run-cancellation.ts index ea38644de28..7ae25021fa5 100644 --- a/server/src/agent-run-cancellation.ts +++ b/server/src/agent-run-cancellation.ts @@ -19,7 +19,9 @@ export async function assertAgentRunWriteAllowed(tx: Db, companyId: string, acto stopId?: string | null; }) { if (!actor.agentId || !actor.runId) return; - const [run] = await tx.select({ status: heartbeatRuns.status, resultJson: heartbeatRuns.resultJson }) + const [run] = await tx.select({ status: heartbeatRuns.status, resultJson: heartbeatRuns.resultJson, + runtimeMode: heartbeatRuns.runtimeMode, companyId: heartbeatRuns.companyId, + contextSnapshot: heartbeatRuns.contextSnapshot }) .from(heartbeatRuns).where(and(eq(heartbeatRuns.id, actor.runId), eq(heartbeatRuns.companyId, companyId), eq(heartbeatRuns.agentId, actor.agentId))) .for("share"); @@ -28,4 +30,5 @@ export async function assertAgentRunWriteAllowed(tx: Db, companyId: string, acto if (agentRunWritesRevoked(run) && !stoppedForThisMutation) { throw forbidden("This run was cancelled", { code: "agent_run_cancelled" }); } + return run; } diff --git a/server/src/legacy-run-task-ownership.ts b/server/src/legacy-run-task-ownership.ts new file mode 100644 index 00000000000..2c9bfbf5b6a --- /dev/null +++ b/server/src/legacy-run-task-ownership.ts @@ -0,0 +1,24 @@ +import { forbidden } from "./errors.js"; + +export function assertLegacyRunTaskOwnership( + run: { + companyId: string; + runtimeMode: string; + contextSnapshot?: Record | null; + } | null | undefined, + issue: { + id: string; + companyId: string; + assigneeAgentId: string | null; + assigneeUserId: string | null; + }, +) { + if (run?.runtimeMode === "legacy" && run.companyId === issue.companyId && + run.contextSnapshot?.issueId === issue.id && + issue.assigneeAgentId === null && issue.assigneeUserId) { + throw forbidden("This task is assigned to a human; the outgoing run can no longer change it", { + code: "issue_handed_off", + issueId: issue.id, + }); + } +} diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 3ecbbdab36f..fba3795594f 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -1,4 +1,5 @@ import { parseObject } from "../adapters/utils.js"; +import { assertLegacyRunTaskOwnership } from "../legacy-run-task-ownership.js"; import { enqueueFastResponse } from "../services/fast-responses.js"; import { hasRequiredWorkspaceRecovery, LEGACY_WORKSPACE_RECOVERY_SCHEMA } from "../services/workspace-restore-recovery-state.js"; import { hasUnverifiedNativeWorkspaceFinalizationOwner } from "../services/native-runtime/native-workspace-finalization-recovery-state.js"; @@ -5632,6 +5633,11 @@ export function issueRoutes( res.status(403).json({ error: "Agent authentication required" }); return false; } + if (req.actor.source === "agent_jwt" && req.actor.runId && + issue.assigneeAgentId === null && issue.assigneeUserId) { + const run = await heartbeat.getRun(req.actor.runId); + if (run?.agentId === actorAgentId) assertLegacyRunTaskOwnership(run, issue); + } // Task-watchdog runs receive a scoped *grant* to mutate issues inside the // watched subtree. This must be evaluated before the base assignee-ownership // boundary below: that boundary denies an agent mutating an issue owned by a @@ -14241,7 +14247,18 @@ export function issueRoutes( agentId: existing.assigneeAgentId, }); const runToStopForReassignment = await resolveActiveIssueRun(existing); - if (runToStopForReassignment) { + const isHumanSelfHandoff = + req.actor.type === "agent" && + req.actor.source === "agent_jwt" && + nextAssigneeAgentId === null && + typeof nextAssigneeUserId === "string" && + req.actor.agentId === existing.assigneeAgentId && + runToStopForReassignment?.runtimeMode === "legacy" && + runToStopForReassignment.id === req.actor.runId && + runToStopForReassignment.agentId === req.actor.agentId && + runToStopForReassignment.companyId === existing.companyId && + runToStopForReassignment.contextSnapshot?.issueId === existing.id; + if (runToStopForReassignment && !isHumanSelfHandoff) { const cancelled = await heartbeat.cancelRun( runToStopForReassignment.id, "Cancelled before issue reassignment", diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index e34121a99fa..3149a777035 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -7,6 +7,7 @@ import { notifyChatPublicationWork } from "./chat-work-notifications.js"; import { recordChatHandoff, recordChatCompletion, existingChatCompletionReply, acknowledgeChatCompletionReply } from "./chat-completion-delivery.js"; import { mirrorSlackBoardComment, slackBoardReplyBindings } from "./slack-board-messages.js"; import { assertAgentRunWriteAllowed } from "../agent-run-cancellation.js"; +import { assertLegacyRunTaskOwnership } from "../legacy-run-task-ownership.js"; import { retryIdempotentDatabaseOperation } from "../database-retry.js"; import { externalConversationStateSql, nonIdleSlackIssueCondition, resumeSlackConversation } from "./slack-conversation-state.js"; import { documentService } from "./documents.js"; @@ -11194,9 +11195,10 @@ export function issueService(db: Db) { if (actorAgentId && actorRunId) { // Recheck under a run lock: a request admitted before Stop must not // commit a late Done after cancellation revoked its credentials. - await assertAgentRunWriteAllowed(tx, receiptExisting.companyId, { + const actorRun = await assertAgentRunWriteAllowed(tx, receiptExisting.companyId, { agentId: actorAgentId, runId: actorRunId, stopId: actorRunStopId, }); + assertLegacyRunTaskOwnership(actorRun, receiptExisting); } if (actorAgentId && patch.status === "done") { const [review] = await tx.select({ id: toolActionRequests.id }).from(toolActionRequests).where(and(eq(toolActionRequests.companyId, existing.companyId), eq(toolActionRequests.issueId, id), inArray(toolActionRequests.status, ["pending", "approved", "executing"]))).limit(1); From 7d7f55e4be07f7157e108d400fba2ab02b36c7c8 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 12 Oct 2026 23:54:01 -0500 Subject: [PATCH 18/37] fix(apps): show user avatars and center access rows (#15577) ## Thinking Path > - Paperclip helps people manage AI agents and their access to apps. > - The app access page shows people with linked chat accounts. > - The identity response omitted the user's saved image. > - The row placed the avatar beside only the first name line. > - This change returns the image and centers the avatar beside both name lines. > - People can recognize linked accounts and still see initials when no image is available. ## Linked Issues or Issue Description **What happened?** The app access page showed initials even when the linked Paperclip user had a profile image. The avatar was above the vertical center of the Disconnect button. **Expected behavior** Show the linked user's saved image. Center it beside the Paperclip and chat names. Keep initials when no image is available. **Steps to reproduce** 1. Link a Slack account to a Paperclip user with a profile image. 2. Open the app's Access tab. 3. Inspect the person's avatar and the two name lines. Searched public issues and pull requests. No duplicate fix was found. Related avatar work in #15095 concerns agent uploads, not linked user profiles. ## What Changed - Return `paperclipUserImage` from the chat principal service and declare it in the API and client types. - Use the shared avatar primitives and center the avatar beside the full name block. Keep full names available in hover text. - Add image and initials examples to the existing Storybook access story, with image and alignment checks. - Extend the service test to check saved images and missing images. ## Verification - Token gates and `git diff --check` pass. - The 33 existing chat setup and identity-link UI tests pass. - The OAuth installer service regression test passes. - Chromium rendered the Storybook Access story at desktop and mobile sizes. The avatar, name block, and Disconnect button centers all measured 307 CSS pixels on desktop. - UI typecheck passes. Full repository typecheck and build stop at the Rust runner because this workspace has no `cargo`. The initial parallel typecheck also exceeded the 4 GiB workspace limit. The full local test attempt was stopped as memory approached the 4 GiB limit; it has no complete verdict. CI passed the full build, typecheck, test, and E2E lanes on the initial commit. The previously reviewed commit passed 54 checks with two intentionally skipped Storybook checks. Master was merged on October 10 to resolve an import conflict; fresh CI is running for `24badb742d331c1b6ee3e90cbd06defefe5cd634`. Token gates and whitespace checks pass after conflict resolution. The single retry after CI runner shutdowns passed. Greptile gives this commit 5/5, with both review threads resolved. ### Screenshots The Storybook fixture shows an image, fallback initials, and a pending account. ![Desktop access rows](https://raw.githubusercontent.com/paperclipai/paperclip/fix/app-access-user-avatar/doc/screenshots/app-access-avatar/desktop.png) ![Mobile access rows](https://raw.githubusercontent.com/paperclipai/paperclip/fix/app-access-user-avatar/doc/screenshots/app-access-avatar/mobile.png) ## Risks Low risk. The API field is additive and needs no migration. The image uses the same avatar primitive as other user identities. Missing or failed images retain the initials fallback. Other chat providers share this access row. ## Model Used OpenAI Codex, GPT-6. Tool use and code execution. The runtime does not expose a more specific model ID or context window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- doc/screenshots/app-access-avatar/README.md | 19 +++++++++++++ doc/screenshots/app-access-avatar/desktop.png | Bin 0 -> 65248 bytes doc/screenshots/app-access-avatar/mobile.png | Bin 0 -> 34172 bytes packages/shared/src/types/chat-channels.ts | 1 + .../chat-channels.integration.test.ts | 5 +++- server/src/routes/openapi.ts | 1 + .../services/chat-channels/identity-links.ts | 2 ++ ui/src/api/chatEndpoints.ts | 1 + ui/src/pages/apps/chat/ChatEndpointDetail.tsx | 23 ++++++++++------ .../chat-management.stories.tsx | 25 +++++++++++++++--- 10 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 doc/screenshots/app-access-avatar/README.md create mode 100644 doc/screenshots/app-access-avatar/desktop.png create mode 100644 doc/screenshots/app-access-avatar/mobile.png diff --git a/doc/screenshots/app-access-avatar/README.md b/doc/screenshots/app-access-avatar/README.md new file mode 100644 index 00000000000..cce5d0be7a1 --- /dev/null +++ b/doc/screenshots/app-access-avatar/README.md @@ -0,0 +1,19 @@ +# App access user avatars + +Storybook: **Connections / Chat management / 2 · People and access** +(`connections-chat-management--access`). + +These screenshots show the production access component with synthetic fixture +accounts: a linked account with an image, a linked account without an image, +and an account waiting for confirmation. The image fixture is an existing +Cliptoon asset used as a deterministic profile image; it is not a real person's +photo. + +The avatar is centered beside both the Paperclip name and the Slack name, at +the same vertical center as Disconnect. Missing or failed images retain initials. +Truncated Paperclip names retain their full text in the native hover tooltip. + +- [Desktop, 1280 × 900](desktop.png) +- [Mobile, 390 × 844](mobile.png) + +The Access story checks image loading, initials, hover text, and vertical alignment. diff --git a/doc/screenshots/app-access-avatar/desktop.png b/doc/screenshots/app-access-avatar/desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..9d56372f29a94c7dd11fde6a4c53e205a799ec1c GIT binary patch literal 65248 zcmd43cR1C5{6CD4m9jTkW$(Q=p^&|Ih{)bVl)W-Tc4qdT*`ksenHhBup(7)5KTn_U z_rC6b@9X+q_jR8?J|9<(_j#Y!cs?KN^^VrQr+N*K8V?N(?b;nRrTb`TSn%VO2pkOf zpSv%<9va#Wv^z@jdVV=Smaxo77gn(M6*Z{K%A&Y^e63sNjOMs~%sOfU`u940tgU@* zd~L5{+fd{q|FnKz_CLNpR?ikYBD1oyH2!0LK5O3I$M@B*Y(8%vA9AHTgTsjoSVX8- z@~xrpsDG~!G(I$v|Nc$h3hfrtzt_{ZXqrm@Uf5ying6|!0OJbZzxNSQVPaeT_dWp} zg17&@&+Z20lj#30c=P|03tDj?*_&*;T~3`}dyHgBd6UO8SDAFaV_TFB9vyajwL<#e zeJKSL)!;oA*hwug@U9ff&Jngedb2>U`qyDlR&~a0!OQ7Odtl9X^dt3I1cQ?u`+r|G z3fVt0KW~hX&uUi`+^mQ~$26aL6yd@7#v-Kf?$r$au>{*z^HO!r4EjoK4&-8Yc{t)~ z(D{kZjeq0gRG5MHxYd}x*wW6V*mAMjEI>$rDQISrndqx^Wja0K^=rMgfeZEUAE%4LU_C|1&$pBwcz>I6E%VX3O)n(?WHQl=lWND@@O0 z=UVsl8VoiyHQ&d@-<^FU!yTvj#pm1rU4_nXrqBLc7>&)#AMrGzsb8NwL0epEa?XI4 z8sOm6gl6I@@=*IOD9AJ7U(=CjgDCY_LHSriTZn6Q>Bu=dvP+clO-A%T*d-$ zr5cMa3E#cN`38ry=YRkHaa(j<>Ao0$3;?Wm+s!Qs<|?zJIPZ|MmN~0sV(8!P~E1iFmO5kwE?`7|BPMJo3YCw4 zu29Ho9q%ov#!>PIAQARs>A0|x3t3coZ}-;U+uQ${#DTVM>1U*7?I3v^ApS|jUoKjd zq5C&Oo1|g_S90=Kl|Hq3VTs8Bnc3(+CnqLKp>3Dv>6Ao{eOMy3ca+TDh>Yg8-7+EOXD>3*cb^*ASmvgsWhzADpiT6 z_S5?_mgt=`o+oSB`dGg0&uZ@#ItpSPV?t71i^bVlgSN_h8HS7$XnCzt{)f|*hSza# z1WP;enl`)iKf6ZM=C;zEE8)G}7l}{7W%T&;a8uyMt=gc|!>_jC-Nl(0kAs{Rnjd{> zbmGrwaa*y;hvkE4>Fc;1;*J>X@cl_YtMacDsHY%_pNP73PBg#hZ}BwqE~^cguNxVu zef=ROSK6Bf-EZV1H>&JWICE#6JPx0w-eGsYxWnXS9m2{$5em5@%N z(R*ih?o7)C{p4e=q&szw;&Z~*-On{ES%MCHpI+b1W>kD;^~(hJZBI2R`~6k7R4IRt zg3;4TgEtEINP2JvlrhJ$Mct~++V0bjS`;ebii);;X>zVH?=VPs%0s!KGC*Ha0<*3h zL)x49eLOFNn=R&+MtW7q#o4IX#FhYIeck=T_$dXB@zGpKQH;mPXv87MG{x$t0!3!k z1f_Xn>{Sl^iq!&k-Pf2`uZ}4{oUNj&D008{Hfqu6jy6W{;YRcc&+P2%e7(JCyBGVR z)be_VhlhZf^>D4r(r;d4cQilef>7k^-6FV+TQC4GL#-D_@WcyWD6yIjAL{L6I) znK%#53Zqdk*v?`5PaM9s`K3?trX6+P`CKzhyw>Dw z_U+w^+v|IXiK5LSC>-q}7ZS}AX@fkvuL&%K)x!~Yqq?nRaXjVYj*pKo6*pmVN3l|` z)D4vDmETura;PUrf0Bkd{}XD%s2EOOE2^GNhLb6lDdbNco*g+`T3SvkCx5VcnZdIj zbgVF>o?%$P!^gM3)ZvSxc39okCO#`?yB^x3S!-gmWZLOh7ghA<_$RZV?e2$6{-P5e zw)5_wQ(_W%T4gd5kp~}M9*xUhDvhW*4pW!O$fQ$kbs5%J^j!W8gu+C0qHI>InRTB^ zh28Jhw+}iha=waFukSv;M)Ui4B{a~_PfYB*!#OTG8dtsQ3N`_yY>1T17$P(3P^mKy zZs+Gj(euNJ*}=q~Ft#7Ee*0D{-N8}a)z#JNk^v<99E9{zi1qcS32m??9o<@LYu~o; z&$4k{?`}OqZWVPVy^;>3hbpN2psH~cU5uIr)p3#XmKpNlZe&X%Bf^}WpKD|$YMl;Z zzxUA1`K---kT}A7;8S}_P23%n<=bFjZ_>oxa%PIvy4FKw`8;Zba z65|z-wj059WKk)pXD7@maW^U^uf?#iwTuq6d+*pZP!96Q%J#g^5?nW)L+FK)izp^9 z+I^r}sG8FrG7vDS-F&{Kl%=dG^wEUzct%NNxzTAV0+(boox4&hqVFbCCGF6y*JcDR znM^h09C|ooIk&awBYa5Xg?Sp{_|@9#yI|z!2&BEYr=MSEup7&lfBGqfL!ZMXQq{7S zA)*IMmg4+px@i(o*H@Y$dV3{`R!m zKY90ON0g_|de@2B_Lm49=hzFYud@m*(WQ5@4fe#Vaw@(<*A{g6II!gWxmv&4%>Va~ zd7W*;SaR;ZS;k4`isUBe&nn)e@gH6a_wQ!%+uj(jFnANgZZ+lk_xyCU0qSw{gAdf= zi}m&zi=#!Jb^hC1TU5lv;U>>hFl`FmXJdN)p5f2al=LieT%!>g_$JxRWzUE&n5JxH zzv3n;D!OQMA3iv3%4M;YKW1(sgGvXlMEUmZ+Z(K!ZxkZ&92Yx$YXojtl&53;k(c2Z zUE1G&oZ35Bb0wIzZVw~IeYw-0q=ZQ4YB1h5_sLV4ppzS+YwBqnPEweL8SY7b$*rj! zhb%?_GOE0S$OOV{-%d=8RoLQq7A>(L;C^~bDhk=O6~R>7P~;q z&!K-$Z;$>ks4@zRkvIn0qW35?hvTDl6^?LB&L46V4FXgjA3c~L4#}o{j zp`oEl$jfRTarj7Eu$|D0G18S^w^3%BNH1-Y-g+RQ zBPc2?9A8dn8SOW$y=c%aen6NXdu{9~?%Jj;Il@(rAzn)Q)sPAbol8$>prtpNEjGJa zy6%pWBoi^lzfAA+*s60h@Yom? z@oYZ<$}yM=BFdwk9)1~?C)JVt-u7PPe`b0c_x#uQagR%kf(r3G?cP${uh*Pyp(X1m zCa`Z0Z~sYHQg$350X)f~S5BO(%Z9^Q+SiAnN@ZESUvI-D?YA!%e2I@6&12r~Mg84R znKk$fi2@Axs-g0!imeCk{k}vJW7-rSl9{jG=TTr*QS36j&>J644DFzm=juV5;AWud4-`!TVQZate zsf_d1el)e0YhS5Tyl%E~9sdl!{=tVB4pxj5GZ!zd{1N}1S?&W)xnsOD>LiP4U%qPN zh6j$0Wj`>{r#C#mz0U%`W`Ai-z)ZH-9V|II{R!P2iI~`)sa*d_Gez5!_*7r$%&El7 zc*JPygu@O)HLb~JFlNLBMoxu!&1`$Re5GR1K;Dd&&k8NTvzr;s_O416DSm@hZ+NRg zGruaP#*wp(lPrZ7ay(gkmy6C`sPi_-?R%vBfdy+jJF!fq0g@@6KbN}>p34o%czz== z_;c&-%K;%vtmTE;TD~Bk>ElYt)1Y{DatLCR(kjc=J=U(nXY&qB#>-|l-_y00-HI2( z4Aj{y3vCwxg2(DK(&LqrZY97YImctjQr;}~xMt5x{XY3v4~m0Z0ZrP>RF%}Cu3W#wD;CxM$nG50uE-k{QVTie)25mN zaH`O+Ec7Ug^E}$tU*!1ySLa#L4;4+|1*~>Ep7}3Izf0FCSeu4 zqkXQfuA>+u=fCKb)mS^Z&06;%` z-4c*Qvc)sVT3mFx8$Dsu;p@Wd*4j@fu$1#x{PZErmw4=TT(oSK$B@i)h>i-6MW-Lq z#H%V~kn@G*K2ACKbWYO+Xu^uKPrh@vvI=g*&4o4YsOX|SQf*K%E~nqC^F zc-dh%*ZMbT=XcuYDyg>Zg}>t?Z2d*;}`cMS~9=wmDHcS=VWP7cZm%y!|S!&Yskdi9<*~pa%U!VAU>s73J zQX5#xKwd6S4r~ur=s#{dqzuuqem=?ywhtIMz9X@YRxD!{uj>3&jqE=fbjT^X&lB!&9 zIF@~hgv9iNgFC7#=){th*Ejvg*@uzO zM5p@ui!8bVPDt1BuX0kVcOx;_&DxP5p$>c*8&gwR8(H8^{|<&=B0$h_jz&jI7Zx%^xIzrMMEY4=?H~q4lk~L z_;4lkGM<|clg}l%u$7VKbCoeDdSToA4~ucG2OjU~6LKW%{g`ja#7eonO_nDUtgvJ> z{J}oHAw7vW*$4X?wO*AGzsrE_i{p)VFF=D(!P6g-?*@g2rP+0pXl#ye5Pv{~b?EYN zb1X0PZ+QrMHbVQg3L5!k;7Oy9&krNbt2Fi7_z#MiZ1OKA*!ENEj($L824>_^c9U%wy@Rb0u*@0qtWw){iD&Dq*I%bjfu9- z&YaFicx|OmdGM(!#k%mMB3z=Co=?9JGi36a{O!1kEOy6Mb`!O(R*sj{2Z*6+!b-)Q!HqTAO{)H2$qWG$OVv z21_Eu&7|8eRT>pBlrF=AMiv%m%r&@8o0V`>TSx<%ZcxvRx5X1_ORI2u4aHGyJA9wr zQ(czhx}C1wC3(I7q@vuo;g(DvB3R><)M+Za?!L`Ibz#FgFl5TxuYH2}*LFZO={9n@ za^;Fne1=2cu;Gn*9tq5e?58c%xrzM3#LzRgqLXEK+9@B-wp8>1TU58|L0n8B*z^Y^gwUPEVz}@7p)<{!t2g;h>`ut@27)obh77nDfVv9|?4l zx3uP|FXpB(pH$oo&+6(=0{-;SM7fg)tza?yz8I8_g@Tr=9ZOcj;xZqXQ>3WnU zLRT3ixz}RL%3>L}FWWRQg7YG&GJK6JMv(0m^F?9iFWt!+?X(o)TNkL?B_eVv$S}`e z>FRWF@eba+KT)k)LUu_@wvvEXeg1~j#jZ2p6t~#bf3}is_~K=RnGoTn{l_GvhFUU{ zG8DMv5D==yNh<$cDmiRwjOWu0C%gHwKLX?4h8wKOH=fRXj4RoSQ6s|`XiR-riiP(ji z2vIZ=R=)p{s$-%p?u9G=rwOOd{ePlR{~yxO`N)*+L_K}_^ya@on2bQrOi9h)wZtoA zLGKbb%R@;3YZSg=|Nm6(&l}Nlpe;R|smRL8!Y#Da<{(2=KP9CeLfrxelmrd^6{uu< z{H3hz&y}7_AYzndmOb4lbrk+e*k&MVb=689xr12h5H&C-_yKAjV5R@$@er)D3N*tq z?E+{JI(VHbDk^I|$PUo*(63;NiHoOk=&Rqjkf5cd1({CVb+Hwf2Eu>V2D0*# zA(j~6E+Agn65bC}e?uW`d^q!Pb2N=DR~VEpZa@c9GVFF+Q1{Kd0=y@S?`(6AjKcz^ zkbOTjB^Y1}tvmAL#^L$VXUhy8bK+DbVb?{m(7)$eucY9sje&?(7f>{@vceD=AoSp4 z&gp0xC#b1Y_V+XI!TSa5N0CQ6hL)BqZJyt4!ujKKKw^Zqz&)LURJ7d>$~P^@143$svvqv$*9c9dCb^;;xP$> zUb_q0<>SYX0R}QNF+mk$Uf4vVPF6^E;?%Do-6@`YeK*$O^z`)N;&_Ed#AO6TAcd;* zgWQ}PY7rMlXJ;Dw6X=shI$0tA>>aOCuv5H1uu|(8OXptb@a28Bw6wHYo1nGQ6B-IY z0t9SY`&e23!;MUrFbphBO-*f9i-rduK)?rvZC<1j@3Z?wSw%$zSS2unGWvC$Vn@3~ zsF``=pz%d+=i4tXw|T0Tb2xl>NhbWzj^;fEYd3)2#a4I4vY39h)6pRyC+}3S z=nwbzjoQ7o!meD!WF<55thVe)VCv}vb=!Eunt)cy@A3J`K|Q~Ig&qK9$pB>8?>hxC z&i3|Ap5H%$PQ1o5E2?QGlggwV1C5-5{XQn=ufbS~19W!ddOMKj4nB4I{{r!I=f@AO z=M#}$JF{=Vd9VfnnVSatUXECpD!`p+$1a~;lSQ(M+9eRw3YBAR%#}4@Re{=<@K8`j z9X`DG2gr62gzVGR|LnJ9bhZ2jIpzbVRE{k}s@Y(?<>lqFPR3CcC)fg^AYM1Stt6;D z*?4k(Q!i__ua61Vl!AV4KX*!Wr*eo&W0bcKtaUYf7&OazyLSNcZ?J$s__SW++f1vw z?S@_B^JmZ2n#yD=m7oZ^^h{DE#zq7HII~!y$FJp3l zC56&{NOp?BtDz$$jW}us-X$U;0$&6`ep`^^T&*=uKTPo9;bEU0uv?))b15O;Ju|O8 zw*Co+2%!!&EC!MY3;S>wx?ZGKu0%;yrO`)nZj+Of6DtX8YwJTb%zq_JF~0_nuemSH znp8R2&+v^&dy>8N!hz-rqmvp>b!gM-}7(_l$FxprndoWOp);CJMkQyAXS zD*_)rd;ncJF*D}L+1LK7Eaa&UI^>dGh#U1W+A{~*K8laY>UuSaJR≪&3j0@0zNr z4huO=j!Xq~`TxQWGYh_RGw}D15z5RAP4?8({Ii&lYb=wXag%cz5JXPFUM~nfSQ}KU zKs@eU0?i6hFMA0y2~JC-E9rn9vcjyx=UT3O*(WBQqOk{S1JQ>hVe~mbA~gX69IOww z#vaXn{%p8+&hym`=$Y4cmqNJG_NxFo29m|@kPGS^ z4vx8h6>Y={!HLEkIl9WBw`u9}@aJM1Nz<*=R~!6q9Rd4@4Mhv1jd`lBlRJ^iQwT5j zKfgZqyf!Uy0akxSA}EQ8@qfUtV?q{g#J0vBE`j)3RaKRwliU^Qx;^_Q*5zs3#gBQx z0k_7?moIIoe10yQ*ILO_P*60kd$R}Xiaz>6%KIHQdw2>Pm{mcxw$7HE^fUFJE;BwsHz7V zL;M@l7IOCc#URSzEF)r4{%D}Q=Hv(kg$lj$$FYw%-3kyqDAZD0?=qj=451K z{E+Lzi8y6tE2AdvRoc?73(dP=Yr*}-cEHWRST<;H;!RJX6uLY9et!AkrL?kJTU*hM zaT(FtVR!}kEX8*3Qj9`FE-!ouEWsr(cw+(-P#HP69nZu^4TKBo?BnSNw$dbiASQW& z6X$x9fZ4y$@-?75oOTa;sB`gWqs)fie*S`WxAtkcOj%U} zwxNpApt+taxMTsxd*)T6?eEpTp%2iIh9#4vuCu_Gj9-i$iBEL~l~I@V!ZRdIt#thlGl zN=^g@&lwC?Xi<0*skw;JyE=i6_I4Y4nc%ZVGqQ;UyoR&KRbz1yg0JVR%_Y5NSF=li z)@;RtLAR?8y2A}N94_w7uh2>UYtFoL6e+WG~TgWNAHs<_^BgaCWaiWJAc*NRx zL*~U45nrK@?1*K68RYh*vD|g39g!qAtNhX3)03nkXZ^>IxX0lF)E1=b1C~7@`aY8? zh+J@STiw@T6feH#h5j3uqCl*{`(B7u+vtZqi{1=7jAxSj#z6JV0jBu=T&CM8PI9VC zX>?n$c$J1HEjIeD)37RSjNkVq#)P#~*ME8slDYn~E5(GpTRvN0uSK72kQG zVo~t<{d5VLS+IRP)`9m#hU3VQm4U5~h1Q6cQ04=%U3<^5Qk2MgXNA$2T+*KNLjb0nEL za(nI0m*bI}>Wxrxn8v@|goFi2a8K+fqUqOHW0_vdjt~;G&bs0{fr5T7PGe5x|RU1YHWk_$I$y^W*a-jdk zgjmhm-tIz6wA2ML>$MX`B`l+J-+1RTouX$b0Ds1V3e2SX0WRZ)okS@HrS9U-apd+n z(O?W23F=$B|GQ*#M*Zb4z=a!4tp%`|w%4Wf+X9bWL2iS*LpVBjsTG$+=Lw*bd#|LT z#exCl0GJ|Uzpn&*FWDLumcU0pHD0Hc=ZXb+@{EA8nJ?+VpFjtn{jwea0z zVgVDnK^Fna(K=YGCI`tD>n=iC`JC|HQMxjVG5iCTl&mQ#GmH^j|h7nK+ z*uh=u1SHh^#OoYoMxRPJknOqw~O1{NZ%jbh++$ zCch$!n6vW^)Rj~obCW$=ER;kRv0N}&X~@e}k(f9RgR5NFiw7AlRu$-{voZ?sU6jm% z&XBk*5bl8urcFH|$bEIWEATdOO0TKrM+;C#uQA930rUf15YRN=eMcZwpLmehSyy1_ zA=~pa)4TLuu68qMVQnW=+b~-|1Mu)+inyZoHfL?rLY3dh0IrQRGBxl7WE%Jv}`t9tE&iTl*7CI+&!d zrM^_?b8;y{EeE^E3z`fTKDkx*Xc2;I=x||S!58rfUD=%IN@_RwQHl99C3IU9?}9c) zlA{pOyEmCZe7IJt{|H`CN73yQ2?gv zbl|ydv1Z%fzdwH;9R^^~#&ivYI-&~cOqE3X;LTAoJyqa4@yMt*5UFw)b(ksFy-MYW zG6uGN_7+dUmU~V}7bCz75Yo`lP)keeJbib*;d}4XtJD<4?2S-a)V4;JL9d6h7YL;W zu=<8T9F-upd4K4|5k&}X@{2k@sAS1n1H4=|K~QJ4Kh36uL{=+7}s! zM191La-U-4%nDrtPF0&h;Z314)ZkNy1bR7M_1!)BAqKLh8L{)Yv zwkQ}40?%d~r^|H8+&H%=fWh$)lVDpFUOo@tmDz12Q{tB!$7xs74gs(%t<2sq*nI}K=`&N*77&auwOeJ2SL=Z- zslCTE%5-6a)_Vrh>1XXe6-O2^D{C_sykrZtEC(8J;>;mIpTuJ9K zr{-^hWMSDn0DtLG$EX1^4n4Uylt9(*PHULUNAD9&AGa=iohQ&EF zA=GTCO#~fHb`|y^LPOt8oQ94IC7- zmn7LBwc>3@!45{KL9r{iv?F*NFK z^AB zG5}T6*6LHfeET>ZBRCTMeSK>XaRfC6AQb5A!N>`Vo)Gg2J@#TG1+N8idxn#VNx|O{ zd=f}>n7~NGa^C3Uyj?y-PWW&vY5> zc>wkdiu%LOhXRO%j1#j~MPeRBpe)aD@k z6X5U9NL(p<4D=1>tzX#4_w8~Syv`fk6q>zjtQEgxHB`Wa3TG>i3OSQpvL8v3Z}SjM z=IQJ2pD5u#UNIkpJ&KC|rru;?W`<8qWaC`BqVN+c`fxnW?UY6+Urbhz-gFES0z&)Z zww6VUYwjmw0J4w@DfrwaaAeWVCn(4XOc81%xQ4OISPW$HsV4(3Lms)fK$A<<5_kp+ zunk10{QP{FG;kUZAjBC?aJ`1;E`0Pemp{LE%RDlmo1eM5mtF9fwer=OU%7*jL@OdG zfnfzH17Mx7x@Qy~)KxWnn4h&UPo%TF)GJXp3xE%s;@D6}?>9Z1xtA*ec*t#kwHJ(R zDd1cvT*f3o85S0XuQyPmn20V0-4BE{36P0kwWPJiZX1J(JOt^e$E$J{@)047;vO4v zXXe0Zr3MNiQ3=5Y5GXaE@JUNap>nGKspKh*mg9*bOu=*qjt?Hpf+m7b!K0ZapxiYV z2s8>d-NO7l+>rWlIitqq!7u~xNfXeY0OhX*$n1%gYZrugl9ULg1S}PO!H*Nv$?_Gz7oXCTbB=i<{3CrZB zCU2-iA74qEmfvTEMhle_UT38>=J_B)oPpjBem=fMLeACms6N?4CF%l7J&UeSBcnkARYbKE^syy9AI0R2(Q( zi?!BIv$CiRYDL|a7kff`Ah2PCi_(8ee^~)3q9Dlv_sZZSj0(r*WtQmwY5}EJ$ zQHzI6=I&oivr{;#B49ZVs!O`e)q=A6E({FJa?jXICtss~61AT+n`#a{Gaw zgnDl{!2(?0+}ylK2uOqJK=O+hy5M(!t{wRmForFb;y;_99kpiK&LAM`hr`KX@PNS4 zX}FZ2?i_OZtcw+HF>9vB&vmnXnSnD&V_w>l9)# z@pyO`8Bu;olN{TjEj**eE9z{FYx~;|-dc;nmiI9S;A3jSnhKNFOfK>*v>l*;E zY+AvmhsqZP5N-jeb*03R?snu|A0=a#4%-u0fq>%6t}r7;JqaHzwoZT!OGR}`Ufuz_ z2}&8Qqz{)#g<(y?V3dq;mLJj6&AQa6Yjf=}Q~q%ya%Gn2M#1)+dS#!E&94Cs9E~b6 z^4?s6iLqkBc^eWE(z=5{_yKe(kQfw_aD|?w+o9P9gt;yBvRl9%xx}K8{sV>=m8Q%X zftiG6;|rB+WMl*puOpMK6rB_qb9RShlabr#MP1e*SU+^*rI6wzp zpT-gQW*3xwX5xI04x2$dmwelf#opcmD0HyB_yq-Hr;1V5jFXc;1b1AHrz`Y#fLglg z13K3)RwE+7#Kg>}n|T6_Z|qS3rU_}-!L7nCuifj}!cR|5Ky*^C-lV+Nv<6TRxA3wg z1k38^Vxw;UO^EO+h~HZt!d9RNv&?}HGj@?4`Oq@hYZmeWMBKo?x0ky*%x*6+cMHl*XoG|3>C;h4tQj}*1j>(91P|&AG zUcbKG1!i*aVs!^v{1w@R6UstQgS|zl>d^yW;^gGyByoWNb>cp~N&8M#%@UomRww*J zfQt_(0ru}Rb;^F5D%yc2Q?tm_JA01eN4bOo+>;n798A^PyM*x^=|MPD9l#HP;L9&6 zOQo|4N9;mg166mkPC@U$VKG9B*BzQNEz9qGBC!uTD3*>8qP6!89I6R8Bde&+0-2i$ z4O$8P&N!6l#ib=3^6r46iuCfCGM#m(@Y~{4k@HY)Y>!lYP|=xiS18)Gzisq00{P`M zcTr~xm_7WS5l{h8c=E6|#|mmh9UsnhHd0idMUYL-u|*N{N0?@XeD&;oNo4R&Hq9~T zUEk+{q3PE_)RNEnIlFNX%L?X^_O}}&sDSX`in3*xA0Hbk)4!>@>Fb>U*_i6Iz&U=Ic}u3 zq|A?9HI7+o=1O83;6)s*v}`IOViXL6HI=RAS7NTeuK%nELOS@6;Lore2(pn`L9>p& z9-{lHDJCf5{UMaub04;^kw~TA_CU7RRWE@^oXCGZ?!6mOg;uF3EH)~h=ezrbb$pI1 zins-;gv*yk_SdSA6ZZgmhUiy9GQa=u;o#44^MjxHF07Z%v5h^xDpb7#y-dm^r8O-w zSoHp6rHapB;g{rm_&7TYY37$;yTV@EgpvxqIPTt6yKitTrOLdc8Q?8SgMq1m{RZ+j zZ(g`%HK6owV;R?_Nz1OAbq5*mX~#Q2L45=t4{-;@7MzNX6XhBx$naPE9U!-XV8kW`vg#?G8gU1{0UZ;aUP6jdEL?aG!a@fz`+9tAvAqxA?W9k~9M}Cd zMx<1>iB}iXe@xyyqmVdHy+Ro6)w@)PRDJLzRrL~g)cAK#vs#B|_LX>x?75fNul#LO z-?Nzt9)%bkHyz_Y3%pFxRYlk?-zAlgd<^W zaC(H8{R_&y5U4r4!7&chOgCZE72e_YryV@X#rk&f>x3VJpI-h>(2Y^mi zF{-zPIj51ta^dE)aKaH`3H>KMJw05_p;I%w`)H}P1@XDz>Z5aJCA*_1NY49GjT~nc z9fNc_2P;JPSR2e(?hi`j0&5u!gByW$2eAZ=L_GTNV;vSrk;rI-4oPH!w35=0$#<)98c4 zlz+*nsYlT?-TNxm!Y9L)9&TW@2J9@|6@gZ&ys1 z2Zu;}e*0W9_$IE@5=;DuO^BlDYGLXc1>(1PM2}C#QT*2tklTjLyCmp{;2nTf!PpaW z5hHs6)3d2aCHP=k%r`DNDMvc+Hz4VebU@2vO9jG{Vv_K&F}|P*Un-b`1EK{Lm_kY7 zZs}s zRx-A&D24|P4#U9(?6*ctqWDL>y^zDbyA1x6@QtMh=Hsl-6FEmUZhzl&Q!>EfTAm38n% zjYUs2YZSk!Xn!m}h3|&*fvaiR`}&@zp(`edES7;A3+{MP!@V3oDp4$Ti64!`=}E*d zFzV%%WNDaqWD-Fz8X6j^7sbm(A#>PwppC)Hz1KXS{Z2Gw%c6;|>|vltH0jT{|4u6t zC8PfzKbp)(c9sk)4~%;w6O+*n@Q*BNt-{L7oq*iz?(W*J17iU(9l8HiQ9iXb4O|i^ zhae1@4B_J7h(V?oCh>pht2(5Rn7KFJXX&@r)(AZe zd%p-GzW6!NMo7_sjxjbotU8dK^AV0nK_9Nr2s}^mq~f!B3y=@%;yy^*PZt0TLfXxd z>chZ*9Y_%X3#LkN%wR*p0{RXjfq=jvurlcK9bQ{0Y6G=pU=o7@_zNoMCvRbo4Yf{2 z5ZV9Dj8abmrZl$Ex96Rg@@F%z*rvIm$bml;)g46zY6Yz8iGRB!VRq0*WDxR&V=coM#hg;_q@tF!GE1{mmckZGP?gbwice zYS^Gg&y!i|41j(~vaz9}t-$lhqcedo!Hxt4ogakL3pjVl_b-Py!K-RXW)-i!74YXb zck|Jvxr-dHNu%2E_vP+jQv*lX+0qa}0t*9<)KU5Z@&5uA9L&sZtykwzPe9uN8yEWv zz{iBbN*6N_fLtP6=Ia5}ew?2_1s28Tz&l0`&qp4!SXAIRESrcomV3{XPG8E+8Tz|4hR1Q$DCfRx@|A1WNMq7-sJ;DpR3A z4Yx_-TQNVL*JhyEfEJgXoed^lH>5HVe0}YJm+C>FABoPVz}?w~5X<)u*`Glw1o_h{ z0d@4K&7;KZ3BX=Bo2V7xqN=V1IUrO#>c*?^(v@~^{HX&lkdKbyrzUOisK295rYWWJ z5jSGQ-F@sLBzk#0906mlyvW!D_T4-L>G4qmI9EpA0q3tYs$;q@ zN^SeHJV^bekQ3zJASNxZt2+fW$NpW4J*X~oNL7{_vUj$LA9=eFTpoV?y7tWSygG)` zNP;|d@GG)u_<`$DqKl?+qw+@?)r01nCJrt_!pCkD_05)vD)KL_HXECpho}o1l~hy; zckBkDhz@zMlV~QKJrSP@K&rB_*Bw%FH7gEpY60cIaUM>tMMg$?a7oWDLj$}SOGqQa z1)AZ12`UaC+#dA;L{)XDrP<&Ba)W)3cDB_x=_nP~85(EA~9Hd4JkuKY4>#;Q00u5M*lLU+VTV8y5t+mFf5ZbxQ^ zB9QnqM6X~ksIp!&{YCA-QQ81vxPOobU|Ax1?M>ahsuQX>Ze7G2CHX#e%L8^=WtU}3 zv+Oqw8FvI0_G|KX52w%kY_BelYjTrg%Sd>;xe9lf%_Jwuu1K@oR@+VragUu*SuetQ zfm02dpq#N$*=#!$WG7>z!cA)%m^=2d$p2K{4qqr$X#d*60KXm|RykSUn3hMc$p9ZM*I-Jb(9V=8vCqqW3rA z5Xom*rK34q<}&9Eyi>{vwp&JsUv$rM%07{>=mp|Q`R(64D^iPY4MwRnu`}2gT+`%XBUVS)ODM82T=uUDd=bG=XpTo-h2yqW8*F09l**a;DfF~X7 zUxI;!*Z63`2c%n+cNciHJ+n!lIX5i>1sK?oHs0_3eH4tVdQNSlYcboJq?uH$W0yYh z8Ndk`GahJJRJ;5igz(yqYGjNLUMK~fTG_mi2q-$?XQ+OpylY6AjV;|le71#!!AJd5 zlZTw1_9oi{xN!wuTmzaCUF5 zTBFiE;m|J-1!xK}^J~!J)Ol?fn|-0_utC{0J4bWLDAmXyOe=%*uB5p5-CPO%GM_^7 z3YX{rE|KyyMZA9rs}A>d6}>ENsu%iPyZdIIX;9CWAebp6gamE{4a?ipQ&2=?2*2E< zX}ivLn6EtZ0d=I*x*>#3H|;{#_}{vHtkILjO`l{7c|YuI;l%k{u(>uQ^c{(>;__)o zusieJK^flbf9-Rds%$TM_IS(a{%#iKUy>0KecjxQlp$q#3#_1!;p(PA)nvUb0-_6Q zBYS&$9&h@;_#r+q?s7iX*1l2i^%VQs{StN7D|`k^0L$Si60~_8@DLHesqgjnN~)?M zdt>wrmd>BEvqDbwSF6`_Nh(Zl@qb=5kTw0Y0S!w_rR*FSfl<+$`jP zrU$@JnkZ1bdLS4ix!|c(7F?ci%YfZoR0?si(4evM%7P;1F;?I#{O-mDU3r zGkS4xae)$pk7%<%UT6Z(y0N}q0i7zE`jng*p6G#IDUmL&nm~Ivbxem^hW}!sYz35z zYb-F}Yi=vQM^zwwz!j=BR5!3i2O4ysM zQEOMW_ebwwg4i0xX2F6m&fom_LfsawEbR){UH&|WOnp;04G4>}hDuR6UA7a0FiEP3bn$RjpG|!LR zrbQj!oZURJC_L!{d=DSImfOwoR_V?=Q04yT57z$o`9Y2B;7dReLIwAjRSD$}P?1zH zULo+)<*^x~b_4P}DWuZ77Il9CN>08vT}c@B8$L1xGio|_x^e94-CbU2XjjsC%MA{`-N zeN0vyL&MKtOyf!alo$$Xf#aAIwMQ4y~WSd36mM@fkq4t&XLV~nP-#(ok+1zk|D zLIDvGpU01Hq&|U+y|axC6EYf=2SOE*GWZXYZio#J016ywFVL#`fBy`oR}gDr+X_5G zmY*$w*}DjVAW)yd%SeceBgIwVQ3Q7nsw4;MRzk2*H$`v{=Cx9i5OYP zN8}1RB%D}4DFE^2CS381hEXrnJ~#ZUf)hix#V0WgpaK&su) zv6%tb#>-m+rU+ySaQY#K1F0b3QS#c_N#IfzDp>(GLX@WWM`fU%GBe*$nt#%d$2a(K zaj_F>BA{ak*9St2nwjEV<)KE!FNkXSA3c0I5U5|Jk2%4+16vH$bA%s~J_8OgQOCM) zS5tFeUgq)YTX5aR!OMZ%37k7DlbskD!O1%SN(avj7++WjgkT3$5IBEcF$vHgj(%93 z!EhWRk?^bmcg^55Ay zlVqj4c{0)h0`;&A!`X1~sD;*mfXjvZ#(!+y_e* z+=G*|Gma@IFqeUYZ9X(ghN%Mfhnv`I=V;f|&gF?cp4##a zL;2dWnGQmZddu1%)w?Svt7af8N3sOJgA4WH{d=YRC>X_k6vq*c$Vdan4@QRW3Z#5x zz$L?mLm1&=sOSwkrp`IihLXV{HE2`!Qvx#9>q}RlZycl+p28=tKIIEJ_d8-=7p(_P z2u#QU95&J-d-&}H#m!li=D>OlYz5U8H`M8y5)DzXTfw@?C;Ce6Pg!o10J>T-BF_G}P*EyuX`9~E28$b;3O=9Bp z>r{6)YV~6}DN2a4$L25$_6j-io!icE{M3PC!zScP$pK%O{R-SWiBeChF~_L+`3NKS zSPbVGr+WuOoGS5T-Pd*9Mqj{BWzXDk=~@({NKlXqLeuvh4)M-pCYM^3w3(O#Nc8u?Y3ggzTiRAq<9AfJ8ZTt zL(3~Ywr$nU@Vsg~U%RLB_*TGC2BUMA``%!{@<5#DN=p_a?1$VCnNV$n-`iqif%`5e zFRy*$(AlwoasYNupFRa+lVoVu`_>on0y;o>ft9Rco=p~kKVF>boko!8YBk|*E-#p8 zRJ->a$o#@t6dWE7~{s-`%&nJ2=&uOf}|LoAAL$21^ztwui(P&_JTxXW|Cg^sd zn|^d~v8YMq1^z1?Gk;AVt)kIV)2}us~QB^_qz6V z67uhyN72bn8DZ+9)+?|}Yv4ID5%q?`pb(dWp1N*{h8VtpwzF&3uDlq)ZlPDNM$X!w zJQ+H>-7x|QDKlMMP*Ag@?Z}ZM;|>}TG|s=)*T@?^mjC4itc=UQN%dFrF3Y$W=< z_%Pno*)wMz)GXV@OoO$6$AeLOlquINUk&FNUt-&HiyH{ZA+OXuc4oe)JlVlKMx6)O znWtiv^d$xNRd%+gE(JmkpClcesY)w*w(Il+V+?2g(2z}@Vqj9Puu!G3%ESpI!NgB! zXWB_kZjCAeqRzZ0flITLp#;VQ(2b5nk*M zEsJh7WZCDx`m{7SDOtp}gPB_3zKnM-#t1#-o(?yM7bQNv&Q2l0@93`?0MDU3l(yPy zMuSPyef&Bk(5n@NQwxSLiH|B&;ki{UxFI93L{`Ar&Kp zl7GJqLE3EU)nA}E(S;t35paBwI*0O5>R*l0c5C1LzfTtUDb(`P<^0&^+IFpPdG zW+F#`6j@K0>FDNw5JE1-#y8mBPSqGB8{u4QWzIyCD1PisZ|_AQOBop%T?Q+Ke&ru+ z=qk*MZ$7v4E$*t_X|NEQdK0r^U4T?%5=`- zDB{BJ_Wk0Jb4he>5S2J8Y*9Q6pFBAXgg;MX?qX+Ay@|7%?CnI2w@QljCaI5HiEWS< z2+cy}W+_Zv5}Vv}|l_pA7EKov#tRAY#jS89f(UHl3g6 zxIz3+>%ta2m$zG#tKPu67X6TL%Qwcad^ z>X7uA_p%F0_8(d#75yosw){N6;*pN$+9j#C77{<%*fpDJ&*Glk zn>X~2tz7SF2k3S%7UVTK2N_w%#}b)mPP{V`Eq9g^ub4S>7=~}ot*SG|n@WEEh@QiB ztYbXNkk9LNBZ+CA=m~ydJ-xOIITsMyR&GLU&6p?PhF({E65He(S5xmR}v|f*#p(`n{=B z=;J9`Cb|KcCL-YSXL_gXAhpQbK0|-$Dx=*)V-z|FLj1RR1Hq$X`FTl`aiNHJivO~} zeOpIbT3QIJi3;_&Gi$6n+wlZKMHOVQ@T2PI%!d`UFgTk{$-Ajma027jt*SN-UN+aR zQuVmoMiMP0{!Z)!WEAXvvKX10i=MF9*Og_Kqt8}mRqM}ngL&3RS63JPsWgj{pg~HU zkJdiAO^UpR9cNI+qV&upv;CQSw{ZlqN%+{P0J_s6f9*f$5r1%I3W<18Yt*KSU+tpp z#ZL*t$Z%TS)8LQD%gJf+SwDN#`X8XiXTl!NXPjM~tGx?$e0AR;Z!m1v@s6E%!+Vt` zK&Q&Vdoi%&}(_-omrtf`sIeYyZWjK&6}JI)w0Vu^#y)k$Is5;^l4WEM&F| zZFfzg76(Bc2ETt^y+4&s?jki7nBRlbe+kKU>qS|%(g0*bWczl7m zkAEHANxTL@f%db{BhCk@q{PF=s&lhRiItTVRA`T*(Oicg-XIg+-t7gsjYdV()bHD~ zA1dFW>oP2eK@e$pzVz!k zfk7sHz_Nt#Jqpnmq@<(}HqdJ>*B@GfTFz)BMYWZryJ!kM8m^$u!CK@Ic7u!Aste!` zT0L>SF|)O4ee!ecLd5Nun3qVSs5X>N_ljGurx0B<&5fFiS90zNp&*7Nr|o{GmH)B4 zhx^AyM=zj=29EWd;p~PcwQ2b{-hqqWEd8pi=edn;*Jbe?tpvH~*b$ zYBhG85-djfO8p-I!!Uq301TU|KK<*h%nl}xv7tI8Ev-Rdl!(kfP=WJ#{7}4V3cVd^ zX=&b#p=p@45+hYQXL*z);A&F8d4wd6Z)ZN99#LIg{lA1kmXVb3Jn)-F4?4o3$Jtp# zcpZblmUr*G(3oSuw~d;bVYzjR2<|KCGeG+h7RdPM&pa^U#inmxl9 z_da;cG&m|Xbi`W;lMbwO=S5ojYU=*=)r08o7@u{EhF8YM#!hyN09^%Z=VNVvBym#lIrj!>HQ$r^xf6o|Fd3ZPGy67#P)#f=p?J{reqW06W7ETqcFkXKh6VqZsr? zVK8*!*E!3=fR|*~ zuJH(;7_TYVU>qtjq5Pr#dB_d;kJ0-Qb<`wXca>kQF7swtILD2>iZC1%{z^$Bz#H-BI#i1uSReYfl@ar$LThA2GCes*t)~W7)3WHkPAsh8deStP+vxOU)4}o7Z(@*Oo0YWhAHdK z3cyI9=THR9q1JETyIotGFcidy{3QWs`VS()Iez{=H>W1)h>@j#1PKk`HwKk2t+}cZ zI{d#+?|MaRGGg_%)*+w%TPTEVg%&0DlcY~!TZbxit^S01xUh7$Ims&+OSC_0D+l5I z`{JUGK+taXG3STCd)B~1m>WuaeI6h&T6o-h{XAp0^XH{xz9W=%c|#VP7!N45`8qnt zY~6VE?OD0>C$0f=x^Vt{9m`L&-G~55Va3PNp)XJhkx*CiSYP$Y5FPOx@3g}=MNxAk zNtCH(8Y&oJ9^>4T?F4hCx4`r?=4PzgLK7Iw;@x1Pa&hp1aW<8_KKuegf-+K4%z<-t zD!7d_p~c7uV{C+to0prLo4flZ{B)p_D7Ej;HH+?qZNMITThy!GM&8UHLkOnwEmJ?o zRc`R*wv|&ei`T`)C#M9aSt*6=6@o$m9|Vm=WXrRx{ZIcVT3&XN!9T3Jxv-}g3!t9_ z=KCP~M8}YN_1%KC=8-OxR-)$wEZY$Sp@m?d_CYS`!<=4n%F?~H3hRTD)OW{b zS-bD`*zm3!1HgD-_JCbRKQYKo|6EG!)U97hf{H(B6I*JZ{)&rEX5ko=Y4g_e zcSuWzv0cZuJQD+pLKKI+Q<~^2{+6XMrbebVX`3@M&QSX z16()zT)ISzN*nw1uG50Ux|u_^PgPSK6FtD+hl_vi;*r_AS0|xfn?CL5c$a`u$m}oS z3tciNw{SNz=PGUHmuEvlxxMoc^Y0zC< zSSsp`pP%~l6>eI88F6d9Xz3-yVAuQ~*OEma1JeTe(P2Mr?O6z>yA&$H>BtmdCFLGy z$L%`aRE%0>z1gW53xIw#DyT~h=~fwl%v~D$s_HI@m$}-MjJIhYj*Qn7Ut^Hr~MadO|0IzH0^_` z1RZckB@UV147W%1aGhy5DIr|Ra8 zdKUKH5PaHJ&%;YIvkaLbxhYgqR8KH-j&E!aG@L%QY0_cjkk_iK%j zHuf1_f2+Rgl6Nl2g=OuS%+v^T5zDPwR6xy{E&-I_)PHNd$Sr>=$&2K8dDp3OkVaQ` zwa41-+4Bf6Hmvs~?P=syVak5BvhBqB`g-b}3JPCR;{&Jv0}8mhct_1oP8=itcFI)T=E0%YSIL_(f!cb)ys0fswLHoHs%o1 zl#pyqvfer!=&i9bpAt%Im zmC{|li;I*-4c&(b(R~wP3FFQyE>6SY3sAn{Jjf`;)qPf4YzzaTeX$2`$xCn>&xUHg zs~NPvE=6~eb!VsW&z%xSlLzihc0&QUUp^OS|7y(TCi+1wUL@%{N3`PN_IG;*r2=Cl zTu)EBTW62IP8bMCrL(7@I_(~yH7!ZXq@rXl!rJbo>Hvjn!&S+G_V9yw()Oq6FX$tt zQ+atu%CJ8<94e~U-1BhlT*gY2X+Y2G*R+xuNEWH1Xb_-ZTOWb~vG1r6PS; zwEaR!K9^t6dx%$h_nt75h~W{YYqYVt@p#^YxDkF)^XzjKvJbf}I6`;5SIKy<)9sfp z=wr`P#iY(85<2O)`)ShFnuF}-9MfWUg+uCH8^#qyZ|5HmGk>t~zTy24&H>l26DqGn zuJ@t6DtRqG1#{w*CSOdh+;`v`qmJm_s?)K;IhDJsx3-J+A?47TpL*hVVn#?4q~t8| z#M?0F`hcpssc+phPWV{zwM_$iOE;9;nJx6ka>hStb|hvgv2cWj$0A*qmzM`%OjcAR zxrtJbnAq#LT_`~+r#p8DF)m49#3O)GTF$$3K>CiKi*rn}bUl1vj<~ z#o5?FYNKj*CNbCdnN>dww`dh3Dr@)K#yF3#r{}HMD4D4!h)mx<>-*pwyNaRoTJz2r z9yVNW{rh3zSAH2d`AQ_s$Uc;RZ{A>08|ra8>erUWjw=5(f?98MFKMfsaM^}hR`+~- z+ysX}&BsZe^dpIKYH8hv7N7gK?l)L?MELiy?RKrl{i_794kWJSYEe>>DZtF)j7QMT zE5UE229jahN8}tAwr}4~tbEv7J8#uQ&KL!`S|VswO4u}E#+A$ICw*-vaNS*!WPzWE zk#iKdcV>Q^&o;Q_g^eCFTnY>#cL}!m3;YLHyAD`X9#Y+2pUmIVbn6EaDZXMzS!wM` zF>=k(ncKnLT~Q0yw!Hd;_p?-j=cbt@!77-W|fUqhVDnN(p82)f2g9Jw{-~prQ+masXPyz zxZy^Nuw_}^Jp1U_gZ26e+Y-Prxi!1me!QJfJ~kbom#Gub9X`h$M~3+a+9%{l|AV3`G-g~#@|9&=GTpkMX<*Dr0-Tp0gh;#7CBAUS1tZ0si(!imF# z6M+{26k%E%DI@2qXD{o+e!Pdn$l_I^$aNV3{YQ9_eKiXxjh7R`-Zb}ccMoln!w`l7 zbVZJ5@3ND#a+)`4Vo-@fHEs)*25Q)=hX=-wm!a%y#M%5ilgx<4Y5xgq<+E91lDqBK z+s4=JBdqQwi!T>BgpK|H?OrET7ymFP7Mws`#yM1@Nq%>y1`s^$jPVi8p%`Ef z37}nwJlB2k2a}*%%lzkiHqL{k0+awi5Oro#7?Z7@u?1<4;2+x(plzqb|86D2X-NK` zt$s?4B(rmM#Y&v;E1=wi@J>NI40OgJ*^4ArfRJ#>;kMEbnYkCB9}rM59Q2(V(fV8V ztC?uHz#0hAHXIXi$uFXpLFGp1d}B~-?>5S8LJ^7%tQv#zx;oW={-=8Zguq-~aj_RI%21-b%5W|#pBYj^J?!7!e?9357p zM9%Nk-xvC0sV*Sh04~7Xa~_imbe-yEw~RQopPS1+1eH;blARV-XyPX-7I3WIw+jCK zYy~wWQ`BVk?CD6A9>rtIEJx0)0!J98gHKU}_7j@x>kfWf_3xqN0}Szx2_a}MSntFG z?t#@acUWPv?qi$ABm?#YldnN|)x<5q!-`(G@2CIg=kV+4Ef|x&f{XrHfP_LOVI^`- zowjJJS-E8#b-`so3A_jogF(NAaXNKZ{9wd;v60;}GVYj>sGG8B(=CBO1ONnt7PS}J zqdZA}x7u5Xcquk_YoP`RF_R?Oba_4>Tp(Nz~->{;gZK*xkiEo)DIYhpS4Z zN!c}E3ssL!^4sCrqwuLT@t`Y6KX92DTwG?5bTFI#hFpVo9UK=YoxuaBm^o{adMuux z;p(WgZ29-mX-ihBg0Zoq90Qm7X~4%qM{b8lM=nH>{_7D!Tjtp+$J7|%tGNSsCH z@*l1v?LTX0=Z1+rgaJR{G0)3OQa(Rym!WuT0j&dwGlHicoc#Bh+2fgQr=(PyL3Eo0 z!W%MKW(rk{+QeH-Ed9zb-U535XI_jMJj-AXyt}n*O>SYrA~=A!IQ{8U31|Q~41Mq4 zx3?FI>w(MTgw6+j9T*@yG8Ztm%!)e*Tn}Fd6KY;ZMs?!s76WRqr2Xy35)W(mWh zPGHT=ixtqypw<3|yhlcm_psQbEFBAF`uqFezGWJ-`h5Py2%0!|ozsKle<=eBVqqtwEk)2V%8h!HqAYo2)TsN<+Jmx!FJmMEy8pm5jW z2!W;=txA`am$NLp0S$s#Dm*gyQSS>^`>(I|W7CJ}5YA9#WhFs>L2)ruy|E`=yS~02 z@wblUFG$M>FNMsdtX8UG6F3>*9-e%4`IKI9xs2mT2y3)jcT z6#vD%y$KpT6k*SJ$V~Z)1DK&fs9~i z6%JApAAN$Dz(Yp@2xuGe3oaZbFzBw@XnlS0quEU=G~wO}h=-WFZonfKv9A>Gia1v) zarvEb$o41=KE8j?-Uz&}A0;F>NrXC2`Qniq7kclf3?+d<_*@Y)3W^E)<$(| zgbY?(2Y#-X6fKd0XM7o5J(yEKn5HHtD=@a`PXl5JHK!@O9}uZf&=b3&<{5>Aa=~~T z)e!_Tj-Lr_@dB=n$Bh&i3gK``zr@+WYzG7BX-Gp<_vd_pgE{AJokx!!R|7tQRfW83 zUtq2`Y$LTnmpg*l5+Jx2=+|UqjQvWbC1|ZTy~jN2Cip67GS480f&StyZGUGM-v1io zrF@cH-S|xdWPSG6ytufyO72{l-i#*}5#ckRm>m{#zkF~{?PZ;ns!)B`qc<&brAqW- zkFN~=jsmgz(vOOp*IP1oiPxB8PD8g{dU$#|2J4CBK7ldH$4bj4^BE`uLEGiN3F-i5 zpDnRm1Bly@sBXa3SMe3~b(ij(mk>S^Rv&_Ias7$tJPj#=-W^4`pc2k)F^JRQt*kA+ zpcME6vqeQP8i5K8o7Zd-pSP4LfslFn&AsO+khj8ffss+xZ`lK8`&_w1pil|57Of*P z=G#$j7|)X;13*NQ)v=0L(XN2>dV=tPXT_^sqY-icD!IocGDgFaf#bBpg*^-5+N=@9 zZMqBscCP%6YNBa+Y0Fr> z!EuOZ=en9^`G;aaYEF~X|qJeX& zN7N)Q@B!7nRm@#rzhgT}KER;97w1Jogrs`5&zH_EUhcFRB>0IGf1~*c%=^Et?reFr z#~`V_?Tx7T8)1GERt~M3tfSO}ZA$|Wq@-h;Ql6EB#*{=na}Ft)8r--4(yz$VN52&+!1MY&|$tOs+cC8vU3zX^;6`uRT^#(#sqg1 z+|+$~@pD2a-$~>+XMRHsDV*PUE+#8$6af0?5BRy}xSR;my3&|CFfTlsm|`6kF8)-C z(ET%+FvXr8FduhC6$74CTdK6cr5P?x&Rs`t=Zz98dqQFop61xpRYArO>`saxv{O3g zq6jt3O&8ZYq75&035vf_u-awK=68~2Jvn59OG;*MNhvjLT;Z>;L${7s2E=rj+aq$! zHEtj%XJ;egQ`CVxwmn`;#2a-I0ulTL6LWKg8h&5^7`k2GL}O21os3Hni zW&hRih%6Rj$_b)nS#j}G{9Qy@LS}_^AWE{}xxU!M0LsulmoE21+dF(mwQqZE5#-ks zEG=WVb^N-+&V_+Kli4F?qjpmvd$_DT{l?=|>OH5A9yiMLs)a||xIzjL1h^czfQAo_ zcIi%hvn4In71UA<4Gp5^4-3BueH$6!-};@22%Ab>K%Klk3=VqS{($LtSHC4#6TZJY zm`zU44e(|CM9AvOh)|6s5jQa1f&kihbcg-o^nlQDy=3tSp6x#^IJ)QIj>AL8QcE4) zTbd23V0e3l{&n!zq?*W9`EBn)(S~Y8>l2IDoWR)Rq#`V5Kms_iR>m@!IQ2R#BxHE; zH0Gwlq@r9+kjR{P$UokSMFAXaY#A(^DyvI}bY@qNfa)9;wpB(#UOxY&F;;}-{~%fh)%IcArx zy~d_LO{p62ye+P3`+63bbf4{iV_@K^C@EVb8t*>mS)hgEYute+Oopf71>p+wdj0Mqll+9zqjQLuut$n2@<{ZNehC?%;{N?v zhsuz)+LxBhc;sA6@G{-Rj$ey}20#&x_6u)DQYch*j6c!IHs3d+-7!DBXb-}?`^D7l z2~x`k=Q2HRApGsLL%z}RZ6@)jHoSfseA#cCVJINyp9T(e8C*3TsbXFqI6q8M;YO$5 zDGAAq?cfve+mvNde%cK2RP1hi9qJv&5<7Hw?$N|vk}UbCnu{KuO6Y8sv+{BxH-7PV z(~az&7y76?W-H}-rtQ5t8UnhbM#Wm0$=8!~Zi>8q`L}aW@d<)ZHmF73LII&MQHDQB zc@z8Uvg;HeCd*&-ZSBPJI%&vhIb2Edd2F=_f`N5~e`>ssMM!`7sDDh>mJ?aRTkNtm zT^XJ2*_i25v;V3-edx=iOcuMG3-40?EM>Hv<1lU*KZyWyW*FnGEwBqWzAfvVpkz#5 zI|Kfy&Tx|k>Wr2z>iVayq^Shd$V5xeCDqv0Zz7na=LxGAoPR8cR1J70vt6$gyyT{G z#R|D3?FILd#Y})#Po6x1!53D7g{z;!J#Ru~*>qRo z52NNC5Zo&~QJ8^b(Va*Sr=7e^LU18*98hG%g3B}EzBLnwA=*cl+SADFNwsnFA?ypI z3mkzZ$AZt&#!Q&!y;2Th8UbBgeQRICwawuDtL0X{iLJS~FVI!ogw6D$)&`Ym9V{@6 zTQrMm4NW873Y|W8PIBPnwqt&(T=K6Bd0?-{TVaRH0gx*L!Ht3+D<4V+#~S!0*2Ojo zwww@%+43pixFLf){TIHBEBR5GCijn$oJICqK+UqhoiwDS)-*9JG5BP1p73;6T%wwe zqrOhBzIoTu4#mACp7hmz8G4D@uBXOJm1^&gD;x%4w*YNUIUqkWTaQQWSO8P9v)yT2 zUQ4};b#EqNRikLvRV}u;_|lfg=t}cwzoLi`6#NX(n5dZ4cT}9l)`RddwKheJ#*nw> zf}?)n*xMQFo*FM9d;*MjH8~m)7^3!~tX?o`CiN7LLRvM{Mp6QalqJ_JOgyN>j8xiX zc(wU{cU-JHR}woLm_}6jR10&b_r1P=Ztwfn&bh+Wz9m(1?Xcx@wQD)jMjf>kRpeQm zbv9GDxa|~}csEU>SINy&^G^;SVG9lw)fHnfvOP@5m|>4^Sfo0~RxT__Jki7V;v;?+>3)OEQy_E@<-G9uf?4tA-`8hQojIy`XqhcaQKN zMW%-L^ujFcH%_XGR01+ke;mA*{PVt4|4cGP>N4$dDFu*XV+A@+#6ONc4_022GdjrC zo!5?_qK@p4gPjJ(z@cYQ|P|(F+98Q64Uukial9UqZH4+KI*EytQ;0_$!Ds3a?2^7 z-St4dqg<@aEh7aPGXc!CRzqq;obU zi;nl#uY7%j?T!Z@6BaFV(lHLFPQjk3f>4_;1@GmWRcN1UdkBd`*W%4vFiKlzC7g|J zu&l{al<8fjW-_@1lQ{9BF>hGI>XTIq=++{q!JAH-g(6dKS*6oQU~wle|8jGx@BOt% z!>scH85a{y+1cND<rZ7TG?CM8;@-O$$d`P9WUAT2x9 z(R&~qNsFFm?>+}qgK#EWhNOIN=&Nh~a=nK@2@cNufcuWl5%P|GJA3Zdga;Pf_S4v> zD}(|}H?5Q_$_vpQX1yXuHCE#{&kG)h^+Jhq2&KJ6wNn1Kw;bIvg4~z-!+q9Q_kA3} z$-q~u>=E!GB8R29xC>{VM(&=rPyzoX%Z-1YY=o7seJng1g__nmog3zBwxi9WLE+=( zS7#eK4F#@Ph^;RUnzI(`&V5>TzBQO_IKSq;Sa_mFra)>{Wh-S^Xym!v;W)xl@*aF4 zHE7iGk6M$6DEd5Lqtih#rrs3ukq{&y;3XQuJ6_{M39|cq229Okt{ba-lg)ASIOXW* zvGn~DSQak63$3|D=F5}KHU<3t^028c-23U%r`eBg=ML~^p6h2Sk>~HBAYG~*Um5Bz zj^+;zuBp1yy_s%r=%Gi~0$$TS5T)2SE*48ey=F%G*E$G!#smQau?-FFk&cNNm=%4- z0GAFPVXWQ9?!E=SM_AGQJHHTE0sd?BKSSqG%9S*LKey)Z;Nr4>hMqyRy0ksqH2_QhraSIhbuQ%|8mTN9 zdwHuI?J^j(lkYz9^2=B(%~4XyMJ}0ca9R5O8}h6Vkh(xRrI*BN=MC!GlqqfpxC>c0j{Lsu#PUQ47q>8t;`g?$RpQg3Qi zZ!a!c+MwQ8KhCf|6dV$xwe+>TMVl^8=N;8X>Us0;kIKslPi7_Rodma17R2SIcQ`jF zcRm7od)ozkCuf!j43`xRm6}#hd%FvY+xds-={9go{TyFHCmKY* zm2fW&wr6A)Z7>a+UuW^y*bsc(<&RQBHnuDz-1h0tfB#A498qG zL9-)VH=un-D^7T#@bcQivx}%=vw*DwUWLeH2Rl0<&AgPi?<5)$?UgpD0T^1HVVge*c-6sBF~@_ghdg9{TWm%0AGYm2B_NV z&{d)#V!PCtMCrnL1CJ(NA)7vX4)-&NejD*HH0bHpr{!gJL;NG8WFnU?iqr<2u4=cd zD^TLhr`dSN?JUmk$Q?`b&Qs+f24vjuf&}%3kkq?8IQA&VRps(8S6Dkbl32s|!!XGg z`|F53M|MY#9`#5kki2x$&WMMO!{Y)3;dCOFm>LGA`uaJne{UU26z*}1LcQ)-x@?mdo+ zifHdHTSf@?1q8ZBm?c01d8)OLBp}Rkty6)B74b8FxB!RRH-Bi9I7%(R@RF8;k0OA& zb%=bUc5dKsQ~8J7#u$%7wbuv=BNMj2OFj#G^N>?YT3W4=v-Ram%m$o+{2fBCR~5qB zAd%eA+iUihZJ8KDwDN!Y0kO|h&hhoO_4U1Jf8X8IWz`ivbs}p!7i1;Lryrr5(-b#EQ8@L2Ond!?qjz zF3?5isGpN@Y%_$E3k?Glcv=Aq!~NE_l17<9?%y-Nghn+rHQ0mV6ZkBQ$A@QO zo(D4!*P)s2QeBNt$D@6@aOm%Eq5#Vm?f%}rK5y&}K*)+)7ZzCvjWAJwmp4%0BFx=7e;rSL)4zuSeS=)11kG^Y_XG-H8G3cx{Y11 zHN^|*aQw@ctChN4I54%B7awvG&y`qGqK|3}<-@1h**L9+6VVY7;B*xLYXUtM6e4J$ ziT!R}VAEXCXNZIGha);Ce77BcKCEWq_{9B9w=wMD;o*syc-P<0HsJ*jrXtry3-l-` zCW6`}3R<7;hg&t-JJg9h`!5y(F~g)bW+H+}%1_SRGBiz~g;saW=w3gQsIX*5@O5JG z6JF1IyiHIxz^QoyTWHzLY{1*8>^Z^3)02~b#P4W(%&~IpJ1(USd>-fc7>@w?iJ^g$ zeA4lKM9+sI8~iKpL~JN>;{H&xL=*{@Q#hh%8`gW<^SVL3>@2x%ZbSnW=l39u;&E}_A`l00j%Eu zEGLCYiFcpFw@2uJ8uV@lnUNdly}{U*gG7maE+QsxZ8O?4-N`i1!s-qpsEDW6SLCBM zUhzdgMgM7;Q6P0c?k}wSx?Kh4>)+lpypcSL9=bDGU7_w%0=>PoOP*BftB(C{G z#d=qI4NJkso?j=nzw__j&Z7*gYYl+#cunxk+Q3C*MYPu*B*vMrJppXUt?bJRgIV}e zCZ3`@ic(r4eT5ihVv(3;%25LW6T)&zAgdUhZNi5E!2&lECA%Cx|2xLV*Ri*lX-BA#UIMR8eNG6g3mOC^&0A*}& zmi;>6n9lhXLPB)=2|}&v`OKrdDv-^YrVJlIIZDulbaM}0|AKy)qS{Fbz%fGR{rh74 zdm}}mT6g^YM>i5ao?;uKAhe)LSvTKv)Ce(#|6tm_3*=YKb~N*hoj;ptb0;`>v)2hC z_($5nOb*>4M^YcIG4kUo1hD^vweeh;KeNtHqoWSa&i#0$c)xgr);lKBmE!gq(f^0N z1;I9uCTT>wf$bG@3lm4v#3Sj+H%skg`&(2pcceB&jlQ6Rg&1ezD>MD&KfV`qY$QYl zL43*lSEhx;Uktvx*uhKGfTy4iaB*=d4639PMVW=667kW6^9coojA49yd|NeKHf0tH zu1mlM@F~3CHkN;iB^2Yo5btX_$&J|csu2=?`qZgYXU=dPSktzH?{$}yd!a+iJsft_ zTG(e9{AOfiB#3#gF|9?lH5XKg->BY*pF~5B)eD+ew}cKkin?85epB?|!L)hO)8rsX zFi?S31HMC|({H+uDr>a2zuyP*LyQ8$r?wB&)YJeKBMk2xrT&x_)a}pk+l*B5%4hL% zva;q~CMTA5BB57$PRS#+V! z!GXfo3nu~dp<-4!{N+oI z@=4;EKK@gYM)DZcCMbC(#|l8t`2(qk6)dcl^9yu_Y_H%7i%|zer3ijsA*MR+Lu7Fr z`J4N_&f-SA9S9&3?E@W!*f5SJB2(#}+eKm^`0n8cJV?4=fc_E0h7!$M@jt)x4vfXe zj@=oB|Ctt)a+;bUzq-CnPh;V;G=$+aS>chcY<`kXO|Ou50|EjvM9cYa@@KV&VLVxm zDD6=9`n6=uCMg;$X_@&Q-B5Jt@Jf1t`rDkJ|AqV}Zc?=g$dK13WuzL-Y0y zJlpG=q0W|(lY3lJ@;f%mH|sJ+J1xJ)A3+osh9d+>G|BC|C2=ftsaItb6%|EmMG$C; zV3dSr3w@nCdIU8Ujz1qHOz(@AO_T6049rS#2`pRkt*aINm(rDd;2pP*Mb>x$4-MWx zaN-iduAQb1M7>B&jeIRJ50xcU^EBOX(gC<^R_0_x!y;zp3bYz^Tng7LiVj0XmXlhvCF|0kZ}pOT&US1Oa`m;r}$Q z(M@!=`xguNXLJKjhIXaQ5g?ZCUtUCXLDql%-OHDUo35q!uP|59zjGKtO_En|(1E=onk#AF>GyK3+W#njf~1TiCc5yP(JoV!>yZ!=WW%49 zX7|m`;2`P-N*`b`Jt=XtMe@L8BLXiC6;*-+Dw4(T-}B6ih((zvqiSna{NI9OU# z^86=t^pRtd6M);%URMHiKoaCdtW8YtCv9x2oBSHoa_%<(3Zhs%G6ND#`-Q`63NM^c9eNvK z@cPiAWCTTM^P>9li4yxQ@;1zp*3sMO76wteL!mq~435U19TqAc!bC-vH)4QC(z6ne zcZlShg5=#Qdj}RL-v-KQRgRBK&Y+S3p`+sBv?!0d!JH&lAOo!cqISksVd}e;nLuc9 zG@Hqp-Dh!G39>OeyCn)w-~)Kf7x7*5$XhSb8}WZ2gxfoIQjYJ`d~Vbf1}hyB?zc3eQ1hZ^Z5H?yf$$0)kx`&lm?MXY{O3yvL^pk4j36URi-(um&;~O7-ho zH#5-16AN>76%oGk*4h{D8{tGg#10S3VU+w?D(uzTmvAZYhI?SE+Mb1WOv8^=MVsQ- z(locMQzuwtpO1bYh}k}dacRcaWhCHnie!~;a8er#STl!EWlMXF`EsU;yS!SG(^-_I zhdK2v;4+L;8lA8rdqE+|S>g+@10|4grhno|)p3n^^R)17ad@y1>4b0Tf(2p9wZ`LJ zG&f@#dfwBsSz9B5e&=^{9)Xjpmt2f576-G4uFbb;896#l5{2AN*k}vWoc?a6j6)_mF<7kJ~yhr^x@|B>g zEqQA2D0NHH(dEO9j{MEm@P z_TK`)!=&6sG+#KuAp(Y8+C2$f`KaPpx1OBD%B;;s1iO+Vuf1nS_q7wJUZh@0^NO|s zG-<)p5G_Tm?}A?$14VXhvV)PUgC+UuRV*JnmUr=cOPxWHIamO;Ra_|c)y|L>1z`F= zF4c!UD7S8Ld}POBJi#}>7467Al;1Yg);>pS76jbFJ}6I6herrN3WgSHdmGWT5Cnnk zvdT?VbD_+{BG5YP+f{7}h?k|LJNuG+KOwCQ>|#oOFcQJxdrTx%~J zhjCYbZ!hzmaRO@F^H(%dOJqxPb2eTYs6IX3UCNHvk(vdfCb`9k%`-OX7^rPd+Fpy%v1WT~ZzJR}gu{u7 z0m}ET_`smek#%4ObyqWRQ8IH;rczWW0tMhLY7ZwZbX?kl07ss*fHo(K?%V-O!@*V! zU`Y)&6`;JTd&=I-$`VjiGVD3ilSk*mH9x{h@SoDRX=5jCV;kYmw2PP7AJGn-7BJUa zGB1f8HvxxUm?*oU$D5BJ4j%3a-%R_3BO|BTxS~*}m>vn$qW{R2ZGH=RVHX7pP-bmU zbBARuUtP#i61x7!QGi0Qaz9Et)Gga@9D0JG4$lp(eJfYpKT@rZ2>7&L6#t0lM`&pE z5ARbcQEKUHY1!2qCZ$FrmivG_TZxSr1uP$+CfakDCWqaW2+FRyN~XxS!%q@a+!2cA zHDGKR52HkcNiFoQ?|O-s2&OO|U9dkzPguNvQuUUqs;XkvQm%Za?0`ZaqB4!T8)ers z1~L@EXF7ZvkB}g4OtCfipjVJO)?|C~CWiZu)V^+|>O$z(PB=xfE3=7q=p6f0+0v0m zWo0HRy}D~wofQUz%QKri<0N6AV4Pb;V(cMf6Rv>wbFC`PhOzA^s#lSWkuP7&vh~1} zJO)2(cA@N=4Ojiq@$KA~JE&LSa+Z`NomeS-k{)dQZ!9>T}(awC_3B)`G zIgbp{CWmP?YL}_`E`U!RQTpx5l!ZRyF@~GI1B4iRoO`lzX_h_(*Gfa>C4o*cYloms z(w=a@bk4$3DbjRX$8VUs{bu}awQ$OI32zAFql(!|1I>jCpPwu)DSxJoI81U~V#sjYs5GxT% z_0v8u-nxq5*Bt1sEfbTwt1+G7=O?tyBXN;m?RRRwi{jzbP`{~q{DG>y~)tD^ppsk6;P-%I4GsUBeBO`BCMXB>%N)MeJzL}a6TyX-I zkjVVIogJB*V!(aVVcY8Cf(#6F2uMup?B%XZtt3(DhVN*fd+#R#d4@YN=Al|kiPO3U z0F~96S?jk_3-}Yr81%LX-sKQYlHCWrgb+|78CoV2hVH^nd!0Hln%F#Q~8-Q@=g?wyVe_ zTJ9U@SSX;T@bSPEJ3FSQubG(Y{335W1Pfl{FRd{*E*;d~`XrqCkEvm2hAZZ%{*#1VmVU}!g`qart!QL0 zE9Y9gLi8-;E#@`&bNB>;3i%OL+2E~k2%QAIG&e~JS>P*lF8uQlp)+$6L7a)DF2 zYhtA(cMy}P`qz^dS^V^**n)(@0|~SO1ANSqwLV#|IU42FBZsm*?nT>ZS7sq(3hy0{ z>v^-T(K4>+-@w-WJXb-5K=1-P{P+@F^uP9luC4s!`#)@LPJ*rd%zBeqKeK4w0$!EH zFVj2&hAC;6U8vi2hlNM7babrEVKgbNvB9-G50eiq-)=XS8~~u19CRwT$G7V|tjXj@ z>z%N}MDsymVdC&ttRc1Fi;Ipiq)Sv_oh~Cq3|NhFQ>c1rYJ0M5CcQTIS`G*f&2Z(00A&10&6DJoJ zjJATmie4r`@mYWuPHeWb`3fuqFZiJ~Y3xK75=mw-6T=|tPCPO6;zs<25EBp=o@Eto zHcLr~G2>fmyk^sq52TCm-LO5RK7QENmba7;kz=j`b5}Xnde}B*c2>Z*N=Yz$P`}_KuMi&L`MP+iog-1(TR9P$rJjBQXlv~INuM}J2^$=x>ErLBhp8DQrO2@)s z-i|zCR%9JBK(KQf8EG@VEPJ}?Oo<10ELt}aDXxTvj(33Egp?qa+jA!e^Jnw*_Y zZ!4m4G^8I9??lVbjUEdXwA3n>IUzRX!5*eao1vxly41woxwY!EU(tS`bA%_}1N0qO zbba&*L<~^& z411sG=x~lT_rvTwrm^=L5>iuB|7dnTj8Dk`3P|kZEhQ%-TOIbfWwfNl93!_z^_FcV z*fh~0F6moLkLcpIMg^d$NZDHIA7PBXAmZecNPtKW|DZAyK~w1KYFC_oqXGrV|5(a` zn2!9vGnZzjf{yn$Zr#GiQbL}bpPZuC){sYkUfMF@6hM3%T`$UV!Zda#d3X^;c3T|= zOBJ&!oXpI>5LV$)!_|#NEr~Pe7-4Idtg`_bs3S^wAU)hz+hkm(L@k@_2qX!e@^%sC3@Qc*QfIQ@=Y zpaxI{UxoXI84Z{pfg&77%vwMwqq+WPTLWe6zcMZXQqej}a?fFeSg<1;6nJ^-Iy$D% z1pdL~>ZJ)o7ZUx0+Qw9Mn&JDs9sb1vh(k#B`p*Uq{OkWEbC;tT284wyEv*WA+Di^7 zQ~;1iUBQGKGsu5@bLb}0{WqTn#(^csZ#SQZjU6g}e4VqmM{9JWc71sYZ7P1dg_iny zHDc(qMn$Nv77o^uvQC(@f%JG8R~1u#;CX!r&t)%&{ju@*{-%S^Y`HiciBX}OZAYx(;#mAJjD;hrQ z=amxSuSn)v7JxH?LYHs`GD-G@8ITR05F*P^^=0!L;@tD7$$l=OfIy+wdP4sbz%0ZJ z^n_P^M<6DE$E-c}%VFpP1lhjb+aBqUuxJAwxKG)7Q;SaJojWHmfTv;>4^N36BcvNB zySie#3`=(#-X*b?30nS#fBb8F&)-W#H1F-bq(be13J6a$s*{lI;!_D!f@}8^KndZ} zinQ<1&&0Ox7qUDGF}i{V$&qnjB0FSG9#%kvL~}@qYam{F>U9me45;NyA!q}-(9Vb_ z{s_8L(2hkV;LQXmcB{7bax){|b#w$Kz+^w%Jv~je@1Lmrg;^1DX2dE%ye{Ltf^WJH zP(G^<=o8+~__uh^Zy;P}apH#0ASO86L_K?rL2dsG5vi|>8sF1J6*6YuDti<)A->*h zT{8=er-^5!qj(#qx<8kHWYb$CMkDCOuP#qwz~}Z3oaaVXMEo$2>M|wy$Iwkcp&K`6 zbozl!?K=TH6qo+dRsTK2^gihM6g4x$_Q z)5RotT5+t>nBr>J!Ga*tH}I4PM&9sJ4Y~sdR_@}p;@{m z-d_9>>|&6dwKok zQslTbJ6p}k9rqV>Pf`=MXeVhGCMQAv zgN0$1xWvd5Sh=AtLX)^7`o)-B;@IH#{siE=3m@wzAlax5`l96};U6_7^&bGcFT=N) zI#BhYTaNBKB4XleK?6G?Ar6z3Wy~Pt6)OI)7uA#^nNey zF!A>QpM0#7v$ZQo6hJ>96 zE^y8bA|U~>RQBD^^r$l!;7_LL03s=9D<*k{q|Z!ty`8<9&)3VZ-_BJ)CqPI+@a*wG zFwsVaGn2Gzk0d-=uz-;D1WSZjq{L-Vo`;Q^oEbgmHW~S} zsE%aTL}{^(CE&HS0T_*R?jh!F^hggmIXNw&>Yh1%6j)olZ5t6c6~m*W^$Fwg?}*)F z%{meGOoc(**EbwymFu#E7ro;Q=ye77uIx~sl~`c^Z|%K#G}e9FE`Dipr_!BLA@oEF zNeP*og_O)9Lm4uLka=h>Wr!ppmHE2NQ^=GO3CWN#Lo$bC%Dj({p67k{-oHQIwfB1W zTJLYI_gd>-_x(J@bzR@@=X;*Vc^t=io-ToW`H?Eje;@H?80fU|)%9C>!t$=c@@TmY zM-Kb9Ny@Fc%_Omc$(%{#}VP=Xqn4YF}SnMj5`dOc5y#v!F~VQv*ov&~&O~+`TAkDPLZGV*WGc z(SD`2K!SS4#ax-k=(dqSNXN(bgqVb9$J@sT#H~3{!#c|0$6dq29GslilqrS6Lx>r- z18o_EDO&f-b5Qah9KI*>SnY4$d-vqK`m4bzl0}e+x)mc#$@1`m_-pDpmry810`PQn zm2b%AWGI>u5DI(1u#cj3%lj4hlBqC{)7rWlBm^7`)E|IGUZ_0S-nhV#3z8J2E)KLY zclaH*AC0N7EdFTCQ8m6Wu?ejufl4w>cu0|hp_dy-p3{!kIYMSbs4WfE9H4)eIe3&4(U;uriRH_J0)paC z>ceN(P|mFl_AZ+Lc(Z*6A8sh~RD{Rrp6K*PChUSlX^MV>$kgQM5&X`Q_M{b()F+u) zOGI`T2~fU1bMM)#v0;D)I~u3Q?+d_YK2btxH9fD|J=i!nlEx*m^M+Kn$B*yMHs8N< z0N({_LQm7BhZ;Vl^SdM-Wm62&1t(*Zl5MK)PE1_iqvo{zy zp%(N;xt%``=+gTjikf`=B~xU~SDerWJ=fFS=O#b|+9O^ogg!0Ws3i5``!tr`htf*l zy~9X-$r4J!bt`Wj%{EYcv#ulg2Myn^m7a%brw-2?=K+evAXP533mzN&JL}&Pl z)5{%9_%XEwhe2l9K8lh1y?ggihwc-4q!QsB&3Hy#D0wAHH_!f>og!+2Fcrj4lcC2= zVwn48<$OZlxCGD)fiVx@$Gd+YilfJJ zE9}Ca4CkwOs(%#a=Ji!j1AK;}5zcp)H(y15QO^p7yAe(1#0G{aV4;VfXX6bC_DEnaRi_?u$v09IiJTvDvTkWzAy|GJYXXZ!i-KFM{ zysJD~PW3;<2(mJ_^DEX+(!|D^H615sfB!~5>*TF1dQSb(!Oxt&-NVd!j@upG2ID`~0})RF2e)vSO_h&_iN zwvC=*5#pz$+(pj*1nvN59u)ZRpKsUW&8>{Y{h)1Nh06=2$Gy*7&nVS@(POI$PGy?` z$))c)lxgpipswdR@6h$)_{y#LqQTrBB!WC5JZ8+)&tpQKJ=*k`O(vX0;Wxztz+(1T z`$Sz2)KJvrllW|fGXwm$il9rqf>i?samT&R0HiDBZt2u}U?g56#<-*}qHfpsn2t_+ zMa_H~8eoG@NQ^a;n2$Zz6Kc&D!p}WBJB!~$qjIcg6RnUtuukYg@d~@2adB~^pK_qy z0wiSCN{q9hx~pBsPRcn|CFzOD7Hdfl-eV>!DH=aZw&-KP+;MsaWL{;g2Axmm+_o)2 zX~%rJwb6=|DUhEBIgPrm)x>%+J}8*`^|7aZ_c)nYApdrCz)toq{Ua#nk zm{}a^{&wns9T+I#!Gq_fy`7yG+9T_zTg9=fT%Fe{92}j1UfyqfD(XPAN*_I^n?Z!7 z>W@cQXYjgheZMkEbGK}QxQ6F)({2P)iqh=GGU58@Il(z>4(NqQcKJ&}hH5j_( zjTM;>WO^M{fb|`zBBnWhnH@N>@p1Ts;r%Yfzbca-JGr{9XO~dx+9aSYEGnuo$ag4s zxAIYXPOG<~poJ1PhB?mXUAwW^NV=X)bnQzugP8(%3B3LJM)xBTTH@}*aYqj6gb8)0# zcJKD>9f(C3WfEt5a7Oj`>yf(GbYtuWl(KIXr=*)|V;>5oIKTZzD2f~YdDedW-VO>y zOapUNQK<0@;!FxMhg4hzkRTK14KtOMmAxaA^CzGFs&BC_%m---VZWt7^H({4vea0` z0akP5Vc0VmLKu8z>{TCsp`jaF?QMdu*w%N?HoMMGG!WH%c>%0R3ZbM`%o8X+b9HnY z;(^wUxv9d%`n6UTnP#;;s8*jYqO?c4HeEG~Zqu(Gth{=Q#U2NJq5{+5=MzT;00H1R z*>nlQ#^3{3%(%9{y z(a}-HFGA}@+uxF}+|$+7^*dlAR7$7baL0;ClF+Wa9Fnz1%Y$kk`n0Y$qtuDOPM;8g ziMWb$N$17um0@JK%&n_iY$?WpZ+9P-NH|XiXlU4~KKX*B1-^4^;4qs&RpJis1p@1r zfUJ$708(apt1lvKLiD?YQ4e4_ZMo&s%)}Z`W6g575cQ$G*J0(>gzkZ#8Ch}r5$_&N z&SyHV4?I{?@n(RkNt>jpEPBx`=Xf@5fz2>Q*T*-+odjk(uzWBw6^u&HDTy8{QIF~F zEwgrSF$UJbs5#{UOh!OOV)=q}l$vk2ZskRcn@NLMBC6JeTg>`a(pfsMY0YF=PW=IB ztw)a@eGt{M5H0rn#*rUEg8F*Xl~?&CTQ0rS(99A(Bpn$U3HGiH08H|d>$i8SrF+UA zzLjH}{VM#Up!`xB!y75PS;Pz3npYt;nT3;K!qf1q6lZcE1X(O*9a;JNr!j>T6WT|KL4N(dXn#yp=DS%iX`)`b| zC|={*x%Tml#m)L$RKz}j#p)N0l3O%KL%n|ZLNlb+D<#%Yt+81a2di3^`k-I zYN!kSjYi_b1sK7+*vi<&g{eYxQO3%en#TEVaCLTOk?*xe6KO+UqB75^*@b4;vw`hU z1K`U@nwsto*8k$y;$(_@z5V{FQPC4UrAXuRm~E_%B{T>yXneQ;qDfXlJe!`0Lg9Ui zLNLN}K^$xZK(Yhm%*0o1*uWmcSb_&8NSBbs1&lyZCiVB1fPd-?b^A{JDfP%qxct|(uO=k-x=^!i@;(Uii@ zyf{6Qu1OLZH|5rSHgITh+OPO0;B)-;#L4A3?3bIT8rp7z&*AG4a|IJq4QQ>d&LVfO zhtW2i21YC_PMjU@-W_Ll4Y$oP@v+p4<2r%ZQzMIhyh}3f-9ei6ZrXS1D`OFp=kU!S zqlH-}!YlxT_Gz=$ye%&kR!oM!cdu54Q*#i+cZ+EB$?Nm8{+*U1ACOT-7THCOf3IgO z;`)c~6x}M7x9(nal&OzW^dD@NwKxc=ZrZ##=0=}M%coCD=i(rG^{8e7);#&<=#PEZ zW<5Kxg%Qw=XE5&)#s`2HA^4Xwaol2Vapc(hG*6}BMfR)@jhx#k6qb(yV6Z7^-r3n= z%gdNHow<1N;)AtVUnGF$o(Iy;un5?y+nnBzDn^taC=|Ra)sxOQ)z(7U8$y1>^NcMp zj~GW`h1!A02K=S=a38klI4sYPQi{guisPA1_MGR34hy%2nEOmdR&*7zcu4XVs-Lg8MeB>8u9d&W-`YKrY@yPOCvv9V0g|2N54vwDa zv%XI_v>?Oo3*ynDxvqa$y@ z<3Gw*b+g;1C^lrboKoaW7>d<=Z%sN97lqZd_j*QGTdXpucMW>3rXeC&_2OiH)O&cD z2+aGew!H3$KG!<6BsBw7=c}lCkhwMe27IXd76FL)4EbIpp3kWn{Y$$5^-=R>d;1J? zD%_L@Ya?^?Nft~yI6&^5@X2b~U8kRBphl>ZP;Jp*o!XnJE7y=!BlaWQ`npFsU!6s% zdZ6Z3VpQ~vinE%%5Iwr!a z*ORZv%0pXR+MA)T;vbB-&gVIOmYaDtrYkx0D(HRwsgHYG9dyjLi8d9>@>$cbl z$L-?cLb~~Y13}@PPcyn_n=H(^(p6tyf6NRtpKfr&9LQ|TkCNGs9lQe-MX!hatgSI? zdF;WEHFyQHxY+vJyECjmI@c_0teDH8qxkPY4Xt(e=-nNC()xRc;ic!xh?%e1#5c@- z$b)G5CKS7-BzX1|aGBlL_#ItVhfMeKY=C6IpyQN0yh@cr&TV=%W z#_8DKGEo*fRgZsE%3Bq$H@Zv_3PcXP!{y@TW4Yx7s4dyVgB-}d6-QQ5X!%E!) zl18(@AukAUUlh%)YiNloM!5Ie1Q&r?r5Ap8Y3p%jz?uzo%M+qW zU;)6K#XH1g3ozGYrj*CxF(aS|en!<$uld;&s}|AHjdHUd#&@G6unBXxGnVJ7={jk$$Z_i5cYYA_zwlH-lZ`rhI(}oQf#8fX{e97PlN-N9GRy8Uv zrjJJXQRm8J^c*U>%=~!mNZyz)9i?o~jN<68Uwr4Yf1*NM*YRxA?z0^zpvsHR%m>~e zyx1cd-A$J(El!49O3mfjA8rI#eyH0KYeuNRyj_-qB0?&7t6we`bH9Q2T8N@k$DJMfUq6gq$p)Ze9N$K ziT8Y8pShaaBgPjo-M5`n#OAB|cDtd|Oy-Tl+zdy7Wsu$q10!Di!vbQdD6-^gFx@Q{ zC=Fw+JC@tX(JL@9F`+#z;RcV1U;6TWg^wtB4F_MjA95`+VC&%-<6_Mu#2UPWyh8Mb z$8L|M#3;UQzMH-bWalmT+h4qgHxEiu~cP%u2DiqClC%mBh4I;KI5j)z~V8UbJQ>k+$W^D!^cr{69fVBTa&= zCkyMBVNps7tXb#JhtZK=by(I;By2luFy*vx)Jqzf5amd|q0EmUj000sB_dcTE8m#CTi?0V`Kl`Eo4KWMWo7^KJ6!x}FP`B(Yx zT(NTHiQ~t&;*ZM2!W@=9Ov(xb9K4E{!hWH?EPBhedv_WBx6>xES$+c?jIwXP_wL6P zx~KYl>B>GnOj$*KKx-K&4!jIj<1gXmwG%*>{KtQZzu;eK!vi=4%n`I#S9=K62S^w& zCz0rcKv@jRCE(9FF|l3q*I)a>-qzPoggdsG)x|wbPPPW;k3tF)oAN+HAm~a@)T#DX z*;IY-_%|*<64E@KQQRN=<>uxF07kB-(Y9S0UiWzj_(!abjRn`*4^|qr1yZ}!Not+*{LXGLgqU5{4Jd&VK3p67>6Bo;J?iE|hYDk#xlqv!0_Tb+r~|@AW41=3 z7%Y$i8fixIpg4dODnkyGwzQC;g{{wrI2-qM_y4;&8ZEd6`4n~0JFj#^n_ZF5)1(F zi>h7L(vk1@+}ql+omcj0iMlJs7456xg{Ftl^b8re8J}YWn_^tl3NZyG#XKj%?bio{ zukir1a3D3+@5azq#He3cDydEFSi`N^uoyh4J|S{Hm!XJIDqYAGE~J!ZMG#QKD8(xP z*61nu=l!~#zAR;kv50T3SpvHPu39@TCoMns@1JVtHc>(<1;H4Ld_-kO#=5G9c2>*p zm7$t`5Pi}(-(7thi$2hVG|OSL!84$q7mf-U^-KkalF}zU$l%f}@GyhjQj(dD zDyCki9nH)+jJcGirlufh#?9^RvFIxiI*pai{25UA4Uvx<){^huO8beqW2=LbH!?A; zrFdQFi+Bf%%Sw8Bq)jp&7)az*xEIdePdS_N6DefJP$_D{TA6uP`Su*$0=Jw|rTD9O zs%PvJj4zZ%idZ(D{l^!XU9_XLE0=Lqg*$Gp-l%58A?0L46stF|^1@30AT=aSZ0M7fAO;xl;V5*pp)mN$ zfm3udfHg-VB*Y}Xe@6J0c9+8r`f^4yBUX_RR(c$6Jd#Y#3TD>?^paZrbC*c!K zM$&w}RC~)Tzir~bAjUm8CW2|rez0Kn+^&nTEOC%mahpNhYM$A(Yd>W>e@$VLL}P3z zioR&~L|k<;N&`%SsGsFPdmH4xXt-8DAOJH{I5wPFT|_#Ua_!B`llP*l&{OBFLRM}%k$wMvJYss?lLl)@0K5WLrl zTmAAKxVf(W&pfABlPoT5uad_RUKSG zq4@1R*pm71z*$Ab2MbiWA@zKt&1Qj_BR&hO`0&Vs{KT=sxo(0ksHk($K)oj@|Jt4+ zpc&`Ir@Y5_xVd*PAN-tm_qieBx%YL6I@86%Mxa8(0ov_f7jRn$n9HnSfyEq6OpZ(Fu;D6Q1l z*@jxzEEVRgkRT&h_+V5ko1}I;zhV!r=RwpOem*nEtx7EFN+MM_-Wi|kW@<$^1^h4W z04 z)Ep^`Pk;$gpLT?y&QV~o95Xv2wj3ys0ygY8P?)(SB-^GBGEg@#Ojx{9;zGVrqYTrvV3M2*le$ z5bl3|NOw%WftwCqgg3lf-YnzTwD;-!9CUEL0mzhC>*q8&OuB?0I$i$!Iv#*jUN zFW(wA`6-h_a3-L!77vHI2nHN$g8Hwd9i|<()N{>2afJNb2F@D@sUAa0PeeCi1f!{d z2tjA9wE6mzD!6sbDMQvfpP$A83nU5s&l2Et5Vq5NlC_41h7uKN92mj2@)s0vHhEj= zlK<(mo=2a&A?4gH2AMzTI^Y4uN){n3QYfdunv#F-x%LP8|2x|LQ#t)#{DqsA zvL-0B?{FCYL7XVRbWK(mXlgd#%aTQ*LwE=XRKu74Qv{S9KeN6Fg|O>zPA^8nd;adE zmv+a>BTm;JsQ_9F5MYbEhi zzEqTy>_ikra~uF;do&}CFw{kP9LaoQ9Dl12f1C37*aw*LysoEj4w|I_|J2hpeiKy z_;?~lk*Td4!0%EK%RD`5m;2deSZ397(t~CbE*>}?kfKy!!AceI+R~{ivKen z8Ew1tb(I-@4JnB5qxcGInEA%v&~p-(*F0hcr>Nx<33WOsox$py|KurTwv1h=tcpx< z?Uk6a73Hhe;6^ZhClmw5jAvdASs45rqBYCZ7{uhn$ ze;io<5lTn#ItDO+NBM4(ub&^9ZN}kxCYq;|BEYC90tfK!&U1w*x1j$5By2T2xbow_ zxPYx&v2k(~!<+8hx+Si)kW}H=1x*SvUdpJ=zF1*^GgQc|sP`RMHhba30lN&(tY5zG zLCa!wyOE19MQR0ABXOX+8v;5HS2#iQ&`kg?nD=O<0A!mdfP{DIn;2?=AfAWw>f`OL z(uGI#=^@VrOcg-oW+2V-Hz}H6@ooM3)#1@s%8y3h+kG8!ZcNLIZuIo`!$NUMItfd! zbon{ZlDKCd=A3Qk;lV--p7ov-a9wCeFNQ7S`MAa8@slUfxE6qn<&UGLY#gcS-uG(3pZ!79~L70$L{L#IKOcEx6|S3L4rhs~Gg=Hwu} zk*P@###2(rvjmru$BFr_827ra27S-Ow{Io|qqEX^3pC>^JFBLu-#ps%1D^hPN1)E-%*-`nl^Ff>$q?NR!uy%zo07Ps)qY z;@*t>(42K)f(|Is2QWuKHy69*k`co2oQ7Z&u$x<6bB_?7UNw!@SJn|iEl9w4u(UA+ zzU8)XHZ>FSO2Yu+#6C$RBOQQxz;^XftvmI^Ay(HD|92ekI*bDOh!JG5Sz6}HF~TDk zr7M!@Gz=pc1p^7KduYh2@TGZwISmF3_q6fQ<|+xNWR=hx^R9hP3JmXiT%#>p5epGh zT00AB?#cwAF?-$i%JDUU{{DwIY<23WXigTgQBDW2qffDKI1s=W#}|k$$HSRtx%x3d-(mvJ zl&3V>RdWp?(y#IHCZGg{$2>N<%t1ZLC?PRZ6!6%6U;i$_<+j@P-L2JE4Y=rnnOD^>(1dUIVbsCp6j_rr71EVt! zU=#}SXtd^uQ9y<6eDy^GL-l86n!Bw_sU{vmBe;mZ}hXj#d9w#h3{$ zKGnRYVIqTQ&T0}n?KuuQF3`B&Xs1T)|EFg0uI}lTOS{Fs$o~-M4_q>TGcRcrJ$I`Q zFoD&~M)U)0r{Jo>AJeIZg`P&Fo8ZBCO+EF&O2PK&`w!|OC5o!5lVt%}h_vMOgU5Pd3N&Lm`#L5{ z8w6rYz*9SboXRZoLOwFWr?x zpW=sE=3e<*2bXSIYqDYKzoO>T;|C#!|z1u4L0GoG)%2mDEHSH~4PymDDHc zw#=o`G&H{tHl{`{7~l2v)i70irZ!afW^?lSoR>^}(A#33_l;oq7t=R`PM`y-+H9-c zrw~p5D~^uVaM*$5sTF$uvIm8HMGo&nFoYVl3&7JAnwj2@8wvp0_3r7GuB=H*kI(=2 z>m3vy{GCAdlMl9T;NWOO9gJKJf^P~KdFTAH!zL>8kP&Y6IG#N44wX$%Dae-SZ&a0# zUyWciul8Q#+m5F$59A6eZEw%4o1{;nTg82MpE8TqPF3bJ?3Tj_X6PEDIS=)Ltg>#+ zPDMLKyudbqm3mEmBp*$B9g$b0>d%57L#fLPWLEu|{)jk~5 zg3Im>K9V8T@oQ6E8!cm`PB#{;$~GGS{s*?nSWH%1U;~L#&wZjE?r2YZ4ICwmzY7RR zSujmG6vVjs;kW*WEX51!&vrbF(T?kKZNahIo-Z_Fq=uQ(L=%&7*B(Ml!OYieP~Z~Z zBi`4eW}|iu0&eSKebKH6Hx=|+bY0`aG)J)L;(d$+DEN6-tSKVhaUgaV;N!)DjRv{$ zB9z2@+8L(WU%_-Q z0skeMZfTK=LaFL%rL>}|diU-fs71a6#;yBENBty=69g(U|8Q_SIufzc`&@?U!^D$@ z&Fv+lo{O^)7~%)2G`SQe1SsLYYzM2{17GO~Q`QRk)+9f)AA7q=A6}unfz-vB zhC;Q?UuprTfLF=>?j9Oqe>jU!W)7)*P>9A)z%i^z)2I~ny>sWzdkn4S2M{KF!U&c& zWX@!6!BZ*YI2`8uum&gR)VfkT%qk)~>&QUm+Z>t0D7`R3$a3jY3O$4Xir5+Cp>CXr zK|>@W{7s`KCo)Q*jUr<`Gc&U>cfB$(l}qZ29KibUY2h6^ia&nj-?y*Y&HUNhJxfG2 zLwfwgmfdF$uM09CqHhp0DRds%J1g7VYcu`JI32i$(Rcyc4!kMnQ=LJDWO*;);G(85 zW-pAUNiqJo`9L4v)YD$6PzM99aN>ws+l1mBa?ev~^kedMg3{?GcgT993QG9{9JCjT zq1ri(bO)Ny5#0?#UDg?^Z@8j8DR?%70@9Kg^6 z=?ZHcat}Mk@6u?rfh1T15RylwTXnRW900`;y40WM11TX$XMDx;v<=)k>AK2Qv4H%B zX9&gh^pEsUPv2ysfI%moS=fy>x}jp=G!Wha7&+@>_~!=QPha*^uyC#hSr71+m!FRl zTYxI$W{MMSl{{cqV>X;co18?-m3XFpT5-T-%jg1;?QYGeIMS`p zz5$bcgE3jrylfF==I%v=H_G7{TV;;}!$D9W%>~IS`idv}!4OPLO=*gdbu}>3_oD-0 zZP7k8!-HYR=oQv(KlK+H(+l-*<-Hz!+x++62Ce+-*ZXanRqvexPY471C@j9M1@1bx zYQdt+!LPf|iyJE7ZM%0RB`k*@cXV~3Y#HXaI|f#PT^sEM6WHK&#t-49whLe=76ZY?dQOw8N%2zb zOrVfG^DKuv+>RbCCZSg4Co1Ur5EM>;4rG0-K;MJUM!Agiz24XHQ0*x#*puT>Z%(oR)AQ5EE{L=7jd?`k#pPHQ1IMMJ!?U*h6ti_W` za7LPP*GH)DUl@KpA~W~%Sj*L*4=9Elx^0+mPS}GcArkD;ci7yKp!!_jrGIDX8kEYU zSq57T`36|c&MpbUXM=3;+`fEHt7qSXKHw~39r}1HBnu=^=fxR~2ZdUJQdNfwY&7gx5+jehR8ez`@6p(kNPawNg4j z`F#dWcwp7*>};r+QO3gSqHm!bl*FGS(^9cS!;b!AsQ$3e-MjlDh}gUIakWAb*pM0^ zRa|IKM=-_ALtGD))f(OBC%~U!?W>ePBp~l4#M-Wf4Xmu@kAo4GqfcV;q8&O9Jku}e zwTPo6=&6O*Xz2(VSXIq1{=}zM{|fs+KPXAdaJatH1LEcz4gJinWvn2pe=x%WjhXH! z*-iYIMu1MifjVHs8F&vuN`sN7@RAS$^iR3gmFYBB(-V^G458JkDazP>ySWrp z$_B#J=`_;5rW&5a?Xxu2a^?Fl5Djqhw7tY30S_9KFZ|-H2^ft#A)$hDBwdb(^BCuS z5F}dt%`F1jMtl9AJP||Z&$>~nsrEFfbIwk8`OmA)KXGB zJhJu82t~ag0*Sc+j2!UZeLug0UTaTKWFIfVmol5Gbwx2K5n<0S0BH`#5JqAEWF3Nu zhfa)+ZtJ;K#9Fict@z*$tw~0RvE=1s6XvjC&(eQ#@cmb`-29jH1vRaWjfXez;>B<9 zb6ZN|B=SSKf+h{8D@IP&ca4nbW3p^tpQ+t7nCVG(Q8;A3bD8Gq7#M_uneiQz6g>OP zTetRgcD^X7^Tatqw>nK1y(E~$C-U;;%LQk$ zAkg)j!Ta$t;Yz{`G&M4{$cSbU0?vzneTxg@F6FhNEhd-1ch~$v^;S>D=%8@;r=6lgfH&DV^bX6$( zK~Yb=)muk;)?rQq(ItQ-(FoYWF-womVTCXzFNl6>BiQj7J=6~h6v3mvt1B`VMc6t< z#w>wt@FV@$WC~GR8q`0TgbBZa?<_gG0!7@RUJ)TQ4g_nwzdE2sr@9q9WwQov!7 z2c}K*;ZEUliEPOp%&B}?IzK-Fb7u;N;6@svR? z2@)-{7aD}1y6Ye-mZ;S)dDd4xGJtw-k%bgwtX~X1BF;OPQPdt1D6B%nN`MNB6***n zDUVTjZAsCJCO1fy`@Q!3WA|?Ou89Tf@O#@Y>d%)xyi?qNCGXq+g9p)1g~EZRUVzWr zYgq!Cml2I`88kkjG;gomPmi20xc@Ai8ayO2E}XeD|NiYq9H}vLo(orPKbj1Vm+LD_ zn`hH+;$Nt+47MCzx~f|BTpP*B`CnaXoBuxRX>&GRGktnRZnjCSr+Htz#K`#?t`xO5 zXE;wd ze5&EnpgpYIrSztXGnhR;Uu!XBV5!F1&hKKh%m$htARIynpvA>CMKMU2E59OCv^e^* zicm4R6&5!PJ@qNJ!%$qGlRmUyPgco|FS)Es^n`0VzD-8hZA$DK=#0Et)lr)KL-)Dt z!)NWSc~>TXYE~xCi5%3YH9Wergb>QEaQINdkShKKHvlo{!4H&94gkkND#fHxTOB_v zOG-1w^Gfy;5tIaeyu2tW>HO%&yRU6_?;VKKFPXgDw$OIMT(^1uS82@+pXQi<)v>8f zMhi^06h!i+YQCW^-5MkDrUxgF5bFB;`5B)5uIR#BfHx;(2z<{@9X*i?(%5|dR4MZ1 zOTY;jqpSvi30-meKuxe!Z@r zta1hnCP;XkQ+}}Pfv^_WYFnENou@}t1PnGz$L)PjKJ0jP`n2Jt-1BW?G{+YM9zl}2 zO_KUbU4r4+9#{1Z0w3PKPgA29r*Rg5Zh)#CO)Q zVK#DNodzTs8%!laJb((5Nv85cyawfMAf@tBR8i=1``Wnq2ceF8rSq9xdCfmMDu)&v z81{RAkFni9QY)N)cJou|*dE{A&92&qn=;^M#Q|MIj=T}!oV@Urhe|6A3C6u(AP{=Wyi@-CMJ8QU>VA_A8zu@2e!2icW z+JESA_(y1`A{1N___b1=H-j2z#yEFW??}rqXKiL@w<2Gd;(xAn*|KG8du?oO`=KBL zN+VQY{`U17H;8#}Ey(sUa2ZI}yh1`X6d^U@`ZijP(gR!_Ib}k=8<*QaL52u}JA44b z0boBj503^_tsk@&Bs6v7Y&6aTMxN<93n&0Xw=0gk1%xH^=7C;p%eH-6W{Sk)4%5UW z5(uV3q<20@jM@!X6-X4&LFbPiy?$->@V~>lGjohKprt3WEgJTY#hVwpsPSs~l^3Ml)P+0Ue3q zS5Q5DmQ91AO`@h178X7(928d3iADtS?o&vF(F%7UOJKx+k?O&JNbp=5H^zbiod8Dn zZ~#--;CCaMpo2~vxF##e-~ah5D0+Cc9)vv`kRXT|^lL(2T&x1T!&T}pRTZSrbqNN% zR?N3%1ef#Ca^@H6d(wl4X6foIMhD_uDrSM7>%7-KJ+n%`aHUJ8rb zxx}VKrGowU$5M?yz#K@a;QSYc?eEoUZ)K<$VQTp@?q4#_@^zBFKI}&RHk2bRp#!H>U zZ5;Xy789m{KU~1D-yf4PPHa{chGMv8v!%Q=0HDI%NzQuteA?&SIvQ)d3s9T`1~ar2 z^KFU!Vr_v|vfo4oeo1yVJcELg1HIF?*T0#M&tmX%d;1|izH-YaAKM)m8Fy>K5}-eb zlbVH_36Kb)^Z?hAiKFA0_)ZGa1}2B@+PzzD`itUio{}>7ju@Ev+#u7H`{VoR;W0$> zZP#ZwPoUT0>|@Q4vT-p=wX(9ZMsAz{>T23B)iT~gAl`V~+&%?iyYS#mHY7J@4U{>MWL+gls|q%4FzFq-n%-K z{G`9MAnLZP1*V@UBzyZ(pGpiLv)z>-6>yV0-J-0=BCotMFEqKAjT%&;7&dxa)=yVuZy!*S zp--c*%RT_9fUoRt8>T`hnmk~CX+~rrXCaU&(uxjkZC%P((^ZJTiDeicDk}xJx!>iP z1^z+7WpjmtTQ4Qtyh(=C)H|uVso!d^u0ES?WZv#^93vpf@?+HRV2bWyUhqNdXVbUz zr2h6BA7L{11n=xZ^90^E+w+Y0N>-<{Vk=qCt%@ZqgpnA=CXv^g|9m&y|HEweAEv|q z{tN#TDj*i$F z$7oJMp_7x?;HJLWxif;jM-SzKH6j32P*9MozZZ*)gJ7+|__6HVTrjghk+YyAG4*80 zYq#A_oS)LJ0 z!B6J_2K*j80Ltow$@a%Av$qK~ls4EJHAaZ_-Fd+NXVm zMxV&1*rCSME~1aP23>;;HU`a!i|a`je<56sNB2W&d*OCsIw@d&Jjhjjqi735(b5f) z`YXhi(?A?uaztDonM^Yt#MU8&>mO#+FAibjx9ix9DREPfPLIzZR+ARh&6fCowT2EkzBg&Dy!1tiriFMZq40iJ3f`r1o3bzJch`EWYVj#~r*5BVf3(#C1AD70$A zpvKZlvArvf8m~o5>qVg@QsDi5K-u-(-3_0lr^2jmQ?1eMt9OU80LgQJ{mCBzM&pl% zw<=hkPV3%hOPpKCA>XS!N~)lDevm_1@IGW+_obZ`n?AYiU23BHiH`y=iw0Leqcnlk zCp52NQswy^O*${BX6wipYh4G1=LL}^7_v8rZC0j3*kT*)<E^Q!<_GBkh^HT$_2>T6s$b|C1p)ZjF*=eo<`VuNAK-=1_uSEofasN z%@l0p`|F6f(9BDklo$AlC@8VB*}Osm@?FY8Xpv(f}(PS zt_uoh4#2FCI*f=`f6?yLwQhoc$YNS@mmMk?F5W=w$*GW&5NR&EqDDDb*kh%)FmRNK zeM4Eufmmugi-S8+Nhoq#Vpnw3UDnS)GvACn8F7>z3)dT!V&Tku1}$nzh5fl~WFk9h zhl8mHc9^j$%^)=}Y-HDoxz(RWKIV#wjZc_Q;+K5Ia{C|~P1GU1x#d^NExqlYPG)j5 z6hU6@{PK(D;NlpY`D$k z7#vB=E)&ll?bU6byEfoc{!>SuK-$CFVuLb8@g)f>$6`fT0*4PFXNoX@s9p~I{xQsE~vQe^g-7E=>HAN?`m7s-{xHu)qXO3$hVZd#m}?0!|q;{Yb~RO++uGQi%sP>3Guj!6no1loU2o1e69w$kT;hjd32eGS>5vfEq<{ee(xKAbNQ3mITLD2O1W^I$?w0NbL0UTI z;``2bX3jb@XU$q?X8w7X?_Qg|pXa&n`?`L0?a)W6^0?S{u>bkzA6%q@tj0h8TnqZ= zpBu`U*Wr~BymaJ0{}B9xl$CnynzoUCGwsRHbkHG>W1?eTTwL5e6n54dO!~LDY(HL* zEW9<+yK&=TbI!w`vKrFYSg*Y#$x+JjSoRQF|G9On+AYj)9(2F)w9dx+w4mDg%ikiO zjZ)PUzneHB(wK%AYgBXr#pp;BK?6MkDUBHxjliPgkVYW_iZTE9zNoN;xbZubM7$6Z`gPna#`q z{2_<-e3AP?gy50r%YmFg95Q#Z&FSjXbeHMSZ;^ryD?RBFUhYE7Yqf=Q?aZ0C_SmXi z>@UuasYRU&UR1l+``z3e-(Bh~bqgh8)T(q^A9mYYw!2JU%rR^Xg7;9t+j`x8#<4y5 zU30p*SJ5I%KukQCgN%JO^jXh&%yi<1)dGdp_%~M8)4a8VwZRp*_=`VfGii$}(;i!1 z2V>@$J{QN_HAfAEG^mFtiSzxzwCPnJ63*C#9Z&mBgK4X01%l)aCa60_x_f`p#> zIca^j9OFC8o_I$rhHUEO&~;mi7q(xtm2@ZWd6Goj7ERZRmGG1rzk|CNesgJTWnXK@7nL$i<9*ceIIICS~V=WrU=35 z(IUM{LI$}cktFnelRF=E2C6*{Q|`ak*jvf;5nzPX=}Z&m_Zlr+P!n=0exqB;k`?P% zCaoBaxJ(alAem<7@i?$Ve-Zf_@d*~DzY9IR&By`OM43ST&x_ zwBmNSsAN7Cg+`=CV0SvHY}L-Q#fc;cm1v2cI%|>jFXlA&+5E&B<@QN@q7p|1_Oa zv+n3AIGzI^c$sg!c*bMD)Ul*EK!h(rV!zxKN}=&yI#A4Qmw=00(3&bw-p$g|GK`e- z^V32_Ve?L_`sn>>0;Sm3E8N~`r)W1&<&;A;ULFN&6#Qm)h)<+v-oJbsC>DJ5@fd_u@Ij+X7ET zdP2&gNXf`r2k+s}wh|b`+t$01 z`D;%%MYm3bnj`Mqe-4kNEruZ!V=m!quq|Eyk)4yvjF(H9^^~;kIA__UG%by!_gOTZ zOgU%yKyDgkLz1u2{B_M~I3@+!rS3Lu3ub2zSf$rvVn->P1G70e`e>>a$kimz?+MOL z*wik4GCZt4KbQz{3b8l2S&@1FHE9X|MOHYzte`w>L(<=od@p zE0D6U4L#Fu_Qq59t@k}$M_hB)^9$l~n%o%-Y>^k;;~FcsV_0d#60{nR>$9oTW7NXP zUo5KJw3kfTb{1kzzH9g*uwRyR)m!Mz{u50Jvo7)$D|$sOw8*@@v;9FeEp}Dcq@JLB z1#^$XDVOu!rc28TCC6K>*+FGf&Z%Wv00qnfd+oi1+3Bp#-+yyaBV3*jMF(Li65?<#Hhv z@;f=#k3xcoc3Z9aCUz4`je2Q4x22LI&2t3bqWj;)yK^tZ)S3*Z#bx1ErQ@1(UFzaL z`Q&w*wU^$T$YOhC#O>n`i7)O*c$Saxx3G>VYzC4xm=cxCuBwY4)Q!Ihd>X@ny4^4t zLK;r=?JwH;cAY0S>-)*P=-l=?>Ad^Y40qqJ_a%`BPetV}1UjIwh?bwYzNYVY9O-Z5 z(pmM@BAL2X{jSm2T!K{Umy8Sg@z;_^WrcLD@5g4}TjqQlN*mA= zJ>y7EcSiZJ$CsG@fDhF)She|c9n*fHZvN%ZHN7}IZ4=)K@&Yk9ywJV7iLn4?o-I9ZxRP3HS>=O}nf#X20I{?(nR;c!k(ez%44Z zZy|-FXXCHG;5s+;IjwqJTwAbJ<&$ct!p;s&T52*~)iaChS-Ix5xPXrD*x5?sam$CT zi`c-?u#HhcDh^Q}GqbZezWRIGMj)W4(lALVgKJT5Rjx+2{NUORbfNmOKfW=vg58x= zoa+;1+=6L&GAz-Lxogyl7k(Px#177OecB3;hh^O+#oy_byxixKVh}QH<&Qr4h5yu= zYq1`KcAp~aWBZWdcRS9b94MV8E8JDv(sk0UOd78_+D)E_IB%>zH__S(w`{2tAYZsl zd0k|u)b%w=)8Jhgy-2vQs%^t3;x#0bLeewCdxT|yC5f0Uxfp>Y>`$8Epq9H)fh;{*6`vn*ZhrMgs3n zaX-%_0hww`q4K>u`J7t9=uB!VB-c{z|cH zyl8U1tE=c4`A@(?`xpKMcX=dv8m~t2ic%a)eQdVPc3UVln)2#4CKeSmedZ=9nP2T0 z;-a6t4jHB~t`qjaeak+pCs#n!K<5}WB3DWKCxeL^zcYlm&bz)O>rSqRQ1G&rBftHG zp;=7Z7rQW9E?vF5QlV&?k^5+qXs3ZM0$v$Y^nWK<55lF)GF2tFby7*WN9Wu~L#1{P zSH?dr*M-_sYZJM*+#QN;RAhgtB8(lWki3wkDaLfWzA2t6Dzv_^<(QE$NCMS@?ZpI7 z#NKL>p2)4}rN3cV)1ydtqGwt4Jg&1V0@jmBcgGjoV_ynf>(usMFD!fExzlXa{I>tM z?L4MEF_-78FGjgAt=B#wDfxWm{0MN`T3Dd7Tw|=rSRK(s3iRW`9V7g1) zWA&js=3?J&8Ow(+fp4<^1BHlmmD(-Y2W@DY!c9{CLv&fnm4{rs-nv)yk5KPset8*H zOic8(Sa0d*Eahar(chL~b8^3gm3;!NkD*-(8~W!pHoH%-ayZaRpN6h7B;fJA&3&gB z@Uqc!@H&6+bfBAGd_Ct>jhCoizr0lpB8TEr7JfE|N%x06_!ZzNqyTj9*~-Uy0xqwlHIc+fj^4(^YQA zsjQkz@(AWUKxA&ai~2t2&HBvh7TAI@384`Y9|BjplJ~af8uL`svl@lyg{AgmyseJ_ z=N@c~5tEUv$c7-0Iok6J3t?p3k!;jLwr@=3b8hqU@&X{0gqqotE~*`Aq0@|#O<{ai zq&xQ2bfVU$cAq71F34fRVX-Yn+{2k!8iAy@lK#?`E-EO6E3b>dqZWzd)~``$y@5n2 zl$!LU9Rq@hELX7Ib3;=N(6vYX%6b^~=f&e9UEwZVmGcNnK@s}+XlR%X6!5OQNU`h8 z(p^^4RWsN5&v+!P02e3HzWT=%x6%IE#G&spD7 z-f^`Q#ncCXCR`@V?Wjc@S=6&+bCr`7Fgrow<4S>D{U{+(L=G0-i4Im)2$3miB;{vl$i(EPN<@ok$sAK2I zS^|XzxM};>Cr$r9e>N+(hMN)-^xs2$!(3xK$;_N7i-@BAlS z7b46m9=(^|Tj}*#`;eHnIHzF;*R5QHYu24&N@_dPb$vKO?Z@uMSSj5;CcyRAq*rji zY8?o`DdsBl<;yXDmRVc8TyST<=Nrc-0L&;smZ z8p&ZvLr-)^Vv};T94~+sb)UiUlNQoy;>Z0Ll%8;V60i4`I)imq*lwqZx^V5|IV^Xr zxxPRby&`7iuSd8_qXlhV$mg(0z>`GBy~Be?hwJqFSAAO{khV+-ue8PBcL+Q90t!}IKGV-I!!FJsZeA~~krvMbE##-vKJ=Lgm z{v-X&Vy32=7)w5qYR;x5T$~U~_di+y1rXamq@=7GO&qKFPd%+q!gkD%NO&%Ro2nxzL3yqQrJX4hu9E-Makx8C3bS z|F%`g7UaMt18lGxfJKZ)B{W;ardwVd%G-ZoyV-Dgac#MJ2Tj!J_u`55<|*3i;Q~9^ z-@X`S#;tgc^_=wvbV+RwOw;U4{PL7v0};nsBP6<$>A4%KQz0Kk^Q%DP19m{n1YD+O(pu5t)HTOQ-Jp zBkvUb(QzBL>Hd~L|Ad2eN)=*Z7mP=(ur=^DS1t1bD8@}Oq-7aU;;htpkNJG3l{TXM5vM1Tes#M?bZh%tvAw7}&ytwBob=%b z<`}j%04bdHdC^3I7ptx<{L10Rt&xTs28?<>=YQo;!19*=yozUg@-}Euk?4*IO7kAS zUp!Qz{lpwHeNRHQS}0}rU;VoKQI^5;@6a=RT8RzKh=T)I8@Qze(~B$>mUr6C!W&bS zhd(B)8{O%d{(kW}?~JFhWZz)e$4un;MspJlgOsvwh#~3TOH^!Hju*1wdV@D_C%;+O z_Lm3mV-Oc|_Y$w)U|N`gfx>;qubZ=Z*u4_*Asq9>rNHqFLF&Jn5j2=FJ|Di^)l&W zsCt^?vvWXK4&nyCbe)EtT#!urT%LpSlgcE2ck=WP@yg};JHc|_q{mu2qjpC@s-w1g z0em2QneStU7z(#X<=PU@H&DKUvRn_+pO_b51MbS84U)CL3)5cCPVZol)nuW?UEC5dxXbNWlhKcU9}DCe`2qnZS#Ztc!D zhhg#CDXNL>ppC2s;L@p|p zU&Z-+KR-Prww4#fB8)N`?{&B_H0+lPi1*=D&X#&;PXHQ!a46h-$$T8u8l?@ zkRdn(m2~bZi%US17^5y)C~f*0tJw*hdg9s1NHL@+hEqm=EX45t@-<;6q&ksI83Llf zOoWMu$!X*=-ut^g;sY9wEaE)BWFjxa9lKVeO^LEy`y& zF}u^G`uNEC>*~rj>3@B3?DSFl!GlV}x)VN8SC8W*(OKl@C;9YTs`nlQVG5z{I1JpI zs&tXxwY_nps1SS!F7UW&p~SqG&KMIz}lvb0AWQj;>5!lflqS50Ek`$k<1 z$f~-Lw2~5|{ti0;+hTa$&oB_G%X0q@q2fHMv-}D}7`KF<-M9IaA?YK*#kC2Qdiu*s z*thz%!92I&Y5f=yRxH7p^t8L4fI2!B>reGG_@kH)BbPlu2Rnc3`P>hK4f!*~c5h1m zCw`{Z$$2XSn6Y2ZIv!6t)~x>2`4vJ4`?u&BtA!OE`CHnkUl8f1)W5PXpw?SP&=PABho3S2P(XO%?Y#kV{P0vNaU2W_#4_ z(K7$W6V%N~Q$&G9e-@l8bHkoA;hu*}ApS_q5Ryciqh~F&Mh)^-nd^|THbUEIF^I-w zUqeMBlo4CBdgYc4Z;0blO-D~tHBbGdZu?yLm=O+_qRI9 zV9`9x{Ceb>N>)i?gk#n*RdMv`uY6?h6j{uTDw`RJL-|-hTK({pKv`aCk2QxH4~4e* zRr5$C$V>9!t_DZs_U=M0EB0t-T^+P?u{z=^-3DXx#2mG~zG3{tms# zyA2n7r{$3m9jmx3B2acQjEkf-qSkHM%Q>q`^n;ye&j;hE){Q72l}Gb8c$1CQY=qr+ zM{sJf2#v7WX?d)QjmQJKTi?qB^D!-S2xs)gi+a(@1N;&he!FmNh~?P`(Ci&y90+!_ znr(W4PQ%59G7Lx_`$dI?$Q|AFU!VA`C(DQOpY0K{WBDMG9MMJ79FRsoWkccu6_|+9 zt7-x^MuS^CpRNBwX^$N?3GWm}l3cJIZfU>%v!4Dkdv7L_+s?ZyZHbt9j<$z}$KZ{2 z-Gixey9EHg-L0_dLxnnIUvNLMJu9Lbn7w!ta2O?HIrjA?qrKRUB+EvT zDmniOVC4fhyA~e>!62RaAa|Px;s$Rn5@dq`MZCl7h(R}V)2RrA9!rEv3rANn_sS)j zMg4d|uKj9t1%;puw#VvBZSC{ zk170W+L@9`f=2(SB@{=Yt`RGS?Fy}2C@Yw{W)wE27fvs^eNDe~(@Yp+FSQ=snbD7H z%2k-`tO>@a6_DhUp$}h-JYJF{LzyPAp*4ivX0;!B@{CZ@UaTS%pc>)Z41ROG&%UpR zLDgPtza*;{Vw|4@!=|a2Pf`g0c(uOwG2>`EnphgaCqE@=6akLAeFBHJXUD{l@buT^ zofK2Gsr$tttio=)Ci7CKp` zO4yyPe@h)j>LWUHqB~OP@#EB2Eq8@R=t6pii6>x!WU4LfwB=DTEkgtstEUiMR?i2N zos5M-Z|aw+nnW9hOYXRy?)yg8T&)aRdEzWY98Jwj1MbNr{q>%GiwJ40Fz@FYn1+NY zPp~R6qp~kkTQEq_qMN$p4csCJy7<;uu;$5da#a1URzB(@kDB3b z02X+9_C$f+Z*+8@FZQILR;*;mpr~Rjn_64dQtrK+t@DMoccw=O0Q-+5NW68$j)78B z$D}niHR%Exhgu1+KZBU=En}!shyV>$480?G2f*)C0~AnAjA-digscjOn^WnS>aIGv z(geNEve>8)N-lP!E*p?$)%LX8xet7=mz0zcTG3y<*wozoa9muL2$?By4!Q%wxa`ya zp#w@3Yd2BjRe9J7jsJ^&t@m(&Hqhp8-MG@ecSIo#`Q#b*Xkh$se(a})}uM_CO4)DN0 zQ1J%(bVU)X4V9H|5eaym>_ZpLwg4m22tI%6`-J!;h#TCsXMCrvyj0@uj-b-`rOWvR z6VGQoDtvx_D~OW_p*=kF!SYv2ZLq5Jq_Gd^Cc31{Oh(Mqk{&cHqPqjl5I4ZcM zN2go0y@}kkv|u>d+;Qm55D#_wisdBYvgrw&s`xiQKB%0XKzL{=P;iweK)S9p7L`NN8KJU9C1$|?6k>9)ZTRs{b>D4g{)BVygZ6*4fH$d1QJ-+*dkghTWI) zUrZ_~wmX=yC5m71V(r9S`w zeEf0NIv$n4B$ey|`C4Y6e@IqG*@0rj z=~v6lC65M5%i}kXUCzkztS;qOy^e`*tp&Y|jh}}B35|A3IK@<9$Tz0)#-{Waj>sxR zj2#Y3QKY=~^@PgU-znJB+`JW?S}1Ee(yu-@w;FixnPu%|wpS%DkMR!(QHu-TZ~8Mu zV9DUMhN5Nj&27|sjNjNQl-}T3%S&`bF;;bJ`7Z4qOxPHcq8AjK_tIQ9!z^A;|7mEc zVP<{5K;NYTx|`XxFpdG}n3J>E{jxqMYs|`_fMU?nlG@Gh6>62x+eHDX9f%|L-97kQ zMOSA@s$jdoRle*KB2Msl$wUF;y^!TYgAf#&OH%n@7*{)yq+rHKx8RI&czfIxZ-+|6 zw^_DOA=b*j{!u-Ph*42&zfXoEdgBt(3zO#rbAtS3l_a)f(Clun%>^i2D-O4C0#=Dn z+Jsn?-N15szk9f7>#6I>5)hpOLqA!K_A~ zls;k6^#$I^6)TL($e(xTxm_U&OC3o6@a>~(hI};KBs{N_qbJ1C5(tMGe)q(-h#KQt z{}@;iwZ@`R#ay{Gf2@JC+|9Ju6-uN&W9Szg_R-s@08x;Nw!Sf0F*xyPA46#us< zN1=9^PZ}SD-V%#Lq~drrJL=?i3VTO1oj-9)GTNm7<#b0p`wGI>Ruy>^9g>Z)Qezd6<5g~@FVTP>ih7^9Lb^dC5K#|%bO{lWvn4g^&!{%o zgXg4ftxC_>o(jEpsV9AcBTp%@2o?l91Xiu$SrC;WICRApT}T9fLPn%u9IUW>?~7ye zR{p*z@6$gFa^V&uMS1|sz`3sQJhm_GNG0%a{`Bvr4QH&^9xQ_^+m9A99s@Z@Ps{$S zhkwSu*Pb8fpbzaVw9WulxQz#m2>fm^YYi)a!H{=>N;*^NvIV^apGJ&)+i1$LLaW4x z1MG@`Fe7&BhL7sm2U{~+dcsUh%Rpn7u5vaD3kx#3(w3GS`Q^gE6vrVA1hUF?nyV*v z6{FIx@yrl&`(Zr={<2oju%$PYt7nDJ<1tF$jzM~|8ZD-ibqg_{vvLfm(HeGM8)&YM zh`aad*GJ%#X%j%Jk5&iY`Tt3ax+bW+9lBRut#-G`;nolKEu2N9N)pd|_zY3!jqMUV zzo{ag37u8B`6!oQ?4u?bmt+F6b%%)hlwcoC{JdbfFqVKaupy!%%oDDfSL1`VD}fbC z$=7g_S5LoNPo*T3IsbWnrvvR>&QJb&MG_hHausxg z?wZTpDb@@pZ=O8$37Sj!nwcBavRGy{XR+a3&;pd{deP~UAaWA5a;4EX_?PnOfa;kK zK_`jVgc8pOK!Nq839LbT1(vOAcgp>HV*spm9lGyd!*>DO7Q6s7UpZvCyM}pR5K-}{+)mcV!_|xXs8_pVa#Jj4jY_|ZP276DEOB_PT~-qBeZNp%PjB3 zN@{r1%bV6Bqb4U?1E>t{>ZkzQxw661<~?Po{EqQs=FjQ}7+#w+YY9Z%aoerBQ>=AqK1Z&fV>po0IbB)Opq zf;lPoiwzpari^N}W$>dETwz-rDG^KkrQiWQNm+&)nApUtmm=XrJ(9zG+1ENPd@0ng zpyFXYbI9dWIj^J6zr!EZjlmB~d$p=G>o)jkHCoL65U&s83||oF0W6~NXsI$)87NYb zt)$t8VES1Dg7M5@hJzX082ImpqLn$J&}-ORYTlchW?Z7iEC%>(xzfbc%&7Tyw?+q~*FSL|%_&k^zhH#D()G!Kj0$Q*v16ng_1N88Gm zES@=`Kuk&|0ON~D*|sv0S~!SZ*wW`-=-4TGEV~#6oS^`jkj@f`xG{ytEyDEm~MtpoOs^RGh z%F}ZO==d8E^M|_Rl^+CN29NGD&gn}ROMm5o+VDVT{z#6^&S2ou~9~U!kMopN)rZ(tipt_Fj{!PI| zW|4;XE1`{p4K0CEqv9gc@+24%EaXp^a$4D12+H_Sz1yqa1t4BjD&ME~oDRk*F{c$` zq@g?DkMMU`5W`}h|Krj6N8_Ijh6dkfFWf$erUjLwJ?#&v-ukEJ&7}m6$Gbm7n6vib zk`@Aa=l#T!DUK*$JLv3ixG-6SGNM1Ty7(8&Juo*w zh=E|i{14}i(U^l^PDSFJFMI+&Cy3%@T}mvmkj2>MLS5pgndoYXeO+L>8YGk$P!JGc zZi$F7Up)QtB0Z)XiB2wOA(O^m|2*Z(%kp|I-vr-8RTb+VNYuXUyT%y$WFx_X(IZ#V zxHr{afZ5>XCTDj{uFS>)a3`#%H1pcFMw1kq4(;B3$J%n{acjV%GN6c!Neu@x}g`?`Sb@ zIH@DbEwh!~5(#h(VT`?)H9QR#&b}#unLTM$HYH#?`yl06h|=8;(i^dcpTS3-yyo}m z$^!45w!nSNKStYPvfJJCXzE5u@8(qHAU&4G0Bu(tM7l;x4MKA9(%d)=5*1Bij>zS^rnAy8I|Il|Z_@?Oy=zzpKxDK*fc) z`DmJbHsIIX)l?H-(#)Wf34&Oct_HE$Yd_lYNY6oo_#aPGsK{vYhZa!$kFRE_2k3DU|R3h!MtAR(6G}S zHbzTqYL0UwgF#bQg9Po=PbVzZt_}UwYUFCghZ{YCRPNIjw8KSu6IE^wT&-GiSD}Gt zMUxGC06OzLtdF4Lz-TZuK>O^4NU#8G&8nJ%7&w9+cvKn^EeKfiO>H$@{RX5>j#ty#(< zh}%>Wo=Mj(A+|tRBukl4SO))b7S+a|>dE){|Or z=f)g&k^3TBuj+G^o&CC+s4O-9054SqJOkMk&qadCSNzPnM|;TNnqC5TNi2!%t(sFV z*8hAz{fd&}d{r$v|7Ak5TyIyBx!H5346lBV{n zCl^fq9_sx+hx)xtmdk$APg(uzQ&6&s8h{ETR5sC)4@U~vo)(Z_gv6p*Ox~)f^{C^a z%Q9Skd&uo5XveJN?DMYQJY$O|is+=NTv@ChfS*xY60N8`uO`#s*367Drxu7HeU;cx zXVPOQs)shw%HivfGf`?vJpCuzE~z1++yerkaXE;dQ%Z?z%w~96tEgF%c0-z*BmP>w zkj^5tWdG_4i4y$7dbC1aX|?1HwGia@PXwg_zY&|kgG|zkQ$$@h;z6|& zeNh^^eh3W|N@B|<2?)0EsAV_TduAoPJ(eR)KqA;BBqOTI0hZOhYVrXYd}Emee_7?j zyV53ezLh6i%ZfcO6Jb4-LMbpr3(6vqn0$N{l7i&R*2JE1^BN5EtdsYiI4eqjk$a-o zp08W+)s4HIkUoNFNuPzVe+fdPOt^v$%gXb7ql8DDtY`j?(m6aRL_S@cbU2nFU+q1$ zI*HK8VuFP1in2gZblF+?rd=0s8AvHwqp%>F+}*MKOYMzMjE zJSJU(@z!)T;Sg|ldhs^|(g9Iag8wVUY1!YgIiSzk%+!FTW`nPu2L$0Ngjq?9PWVKX z3gVJ;1d#;vbQ+lm7>=+9ZdRyM<{UuqGSVb2Cuww)!)r2RE#{3AWK`e2+`0AW_YIEd1s<*dtcw&7*J)>5;l}BrRVP?@7yxFPwIVbks zc$v*5VCe27-UXmHkifF-1lW28vY=Y3fYrYde$nGksLIc-JM%Wcs9xJt-}A=;zF&dK*kpcliKAJ6NXSW4 z;yO#{HVmpsih~CWl@XVMZ}BRDtHx08g&kuSl8Z3yiqE?kI!`vY4+cE+C&V zYFS}ix;$@F)0sb9^>x-7TNX({lpRtIUOuAG4 zCg@o6>P&%637Wuksd>~wiR_i@3X(e9M6krupj#ByRXZ(!1e-S;pe@C{dj#v+`1AO9 zCG$)5!8i!p#h)QI#?t&rGmppoN{g)ZxdddDnhw(-X@d`Rs}8BQJz?RmtW!vy(IB&^ zth_))9>`OpU6u$`;Fpz0*+2dGd}W5#41!HeO2ZC2ymsd4j=Vc1YiW*aMgw|B>rJ^~ zc_SY>(h4#x7I zakI^GerCc=kAs{TUa?>^Njxj%DsJL!ez(i1R~DnO`0uDYgXZ#m&)@ zM$azDW}x%&b^{prS0*q-Ks`EVUwH4a$62A{*lvqyoAU$V(aEhx^i`>5Og<#f0|D(NL$tj{;YXy{#ib$b$oo)(nBv8@q4&|a~ zBGTq~g$)VTvD#ako88FhJ_A@nJtjx1sAhm#U zRynN_%cRmJ{T)J)iQK(Gkm{RfsL@IaW=IHU9QyWBGoUffpx<(|xIToM_imK{f(V(V zmx7H}F@~mH|A&te_EK}g4rz+PN~vilscarZ7emrn>uY7I8piSzo&qN8EH!u!P7H`V zbwcwD-ToUL02%)dC~>=Ca8=vQ$cn;z1ZS}8b!{A%tCgKjq@mimM1Dq?vOVToiMJ|= zQq7Dw{<5FrP}RgTZA?)iHz>(iqTSPy&Nf}3(!(tw`PEBD2qWv}J5i_)C9k zTrgT47=J2rVCh_yc?BS}dZxc;8T&CUWJVd1W}2e>|I~tmRm8pzzQRK8Rd^{0P`T|B zXsX-eo8bSHN%VhM$?zxwKkn>4VIiUBcsW(JV{FAZO57}H4DFCL3vY+P^WJr5*CjEgE<rpCdP_3C9%(DSB#8&nN@~^KE6C=>c{8b*_hZ+P0_~tEO*HZLD0m>zNwmg`P@g; zZ~rE`kaKnKIAq3*Waj!8=I6-gv9i3(h=aJkiXe1gjL604CPJLNgr(KM{Fli;X8ZH2 z*0S+aMu{;4rXQIIjv*PyC&pZkZC&|C0Iw;K{}qgG#$OvC(a&_atKmypNtrcudho{~ zOCB|?^foHgX*XV&GV!8Idu3mHr7Q4~H?OxTO_O)OzeO#iffe8D>L0@=Rs?d7os-VqMyEm`Z0Mo`fRk)vVET z!OPQ{r&_%lhOG6G^nL(25vtyN+h6r@9w0Oll5JQc8mB1f@P3(q8I8vKaqE02-ymOz zAlTqpdF&Cr5E^u6g<^-g$;$PjN~$QGj_bS_%%2hMc1jRt8Y{oWyE4#B;lsIHdog~@ z;4vcE|NHIXvUx{5St-lrsJ`UkOTEi%ZS7r>eLR75@mA6`{>3(@gKhmtf&{5m4D&yc zqMMjU9hm4!FzfaxSjNyiBJ|e0*C~v7f|uKDVMHO*GDYXhjEMS3&-)f4FGSxFu`~5Tpy0uh3Zhkd0$T{;tg5HysKZ~sHqk&4!aM;^LM+4#m zZjcdEejm9X7(xoVz;Rxt_uHGdA4P?nEOn=nQO-dha%w;q$F|TUpskt3g_S3Zi{s(W?i1tUTH}D zK2J?oa9wfp#yu4k`WqOiNQ=1nf-^G4tEQaK&|LUUkz+c#( zKHL~*#K#gY2Y&I0heSW5qCbFx8sH>dMGvD3mRX&T??w`HEG&0}wJd+P{qiBgXc3yP zc8#a7ws7kL{!cm@>E}XGP$2KiVec>u_<%mubgvS|a+QULRR9^%^9`R#v0$Yc;wF#e zKGJD8c;8#^RnPVt(XD9Dg;u=4rG+daKi`UP+b8+b^K9)EvDitgti6%(1#k9%UNLIG zO_`IfAPeO9CaN00e87rUbO!9o@JMSi&JZ9%z5t+$W(FguCgx*ZeEqqhop}&#iNV;L} zcp-_LWrA+eUHIYpc^TH=tlf~f+s;j72$4Oxct}{{m)(is_ytf^!(<)nkieL4#`CJ@ z#i~glzlDYCKz>A9bzVpHY9_n1SHfe=G|}#QQxCIB%05gl1WRBHID2}6d)|@2xh@-z zs7}0{SXlRI!o1n6mn2ofe}+9Ka^@NG!lyX2X|msc&1n9I#LU6joJGwDL5k<`t~eBR zrmefOJk#E%R%n5a6X5HII|STRhpfLck=UJvI-VGkxexavswgDk*TDWMBw!^#Rsn}7QObtXU^@Qy* zM8w;?0hc`<($Y2h=lCa2P?tATW9*Ai|Iq@tJTU^(bPy>4PeNC^{s|QH07wELO~9h> zpBVetn>l7*DmSkTBSTfN`W->0zirl)R02R2(uEApG-`GQO}IViP{BnSaY3 zl1hLsV!s%o{ZLHQU|v}^a5U$PPu??Vqcwk$l#JFxf=NdA>}EDAmjn|R-YGNFX2Y%| z;|}l6&Sjjg*3z-h=H#(v9*gkb1)i1t9a}v4aDu~lg9#X*^@5-W@UW0TnC4CVNO>a# zOU~%|)y%o<#ZlN?#E^<1T-~P!9B=loUGoF)sT;<9v`p!Ac^VVJ1A+Jt#2+gIAvJ`Y zt2$zQI8cxSYKcKlhHXzqcvxs?<+fj=&!4-3X^S-jRtcA^QEDqqZbY`cJ1dRfeZhNE za?QN81c}Kjzsn^{$1i44rTff$nMAAWDrFkGW-As;#oKq4Tag#%nKblG#XV=oq2uC0 zqH;zxG59vM9CNiAaR}+u_%bC~y4M-(Q#g@%?_5m%mZKtZ!hOHbq`6fP)fGjb` zJDH)kYkH-0(Z$Hm(`k6m_|8s-ZU5!h^9)Ai?DC^M-nq4yh2-3mZy5ajSqt}DjV&z; zO7cJXbITQ>$%lvMx|Lk7nB!<$yYnP#a_YU0DP2=DBlZk;n7EtNQ%q~~_7qO9pj%{2 zWljxKBnXYxkp;4kf7iSfv=4u&2G_;02^H*Zo=#j_vtduh4yacczMhS?iRsgKs z5*NQd^e7+TZfcZlJu;4<1^C3h{7HRMEG=zJJG;<`aeOu6`p8gTyfk_5c`+hK-@VHv zs!M_#XloggEmM4BYPKB5s*!_9_kPv8Sq{^V5UKJMbxr4!dbS)>6e#N@V03=bE^Q%{&cOoF0E=#b z2bqHP0Fy2&?7u71{?EKfxKn^SNhunA*G#c|O)NNYYC?b$Yo7PF{sz*`@tGb2!(tQOnW!MI=$IqL|^o7)yKrNWJ0mz>L;2Z*KvcJtB zDE8+n0uexfy!#0i(C%NmjKB`KmAwX$qd6OmkB{C%wq+Lx_&JCUSCKf_aLn$IB=WEQ zi11nIPIcKBf| z@QgADx8dQC+1NtHF}f#^_G<;WVX??YrL3Z-PA_Ok-2| z=Lck|?kdC2X|VgioFQ4L<0K|}@~!8O+u~RUK?s_kpO=2tsF|;s*MmXpS;rTIUOhGA zB7+HDZ#9I9T8&Y6Nk0Z@|7{{x19Isb2)`-uPzg0#(n`aBai=B5rQ&jqK))*sPO&Nc zoQP+dkR83X4Fo5t>6#=Fa;WXnRVCHO^~(((v8aDvW&J|83}}UEko1^NQ%)ntXcdIn zI@T384+|ks0cNtih#s+JYb3RqAv}EuL$vBC%6@Chgq$P>$Dt%};w8-Ay~+Hog^k}8 zP#nPm+TSeH4__{WyA5+KP4~dy{|j1{F#R}uHTfUn^agzNAGa#SVWWkJ6h8l+C3OZ; zi*=~lG_GDFvxwU+qGxKgKYMN4)i&~e`9X@(k<>lAlB1E@iyKaF7*!*k7 zJr3^WX8@oA;2`{o6fF@4-^>;f7;?_tKwtmVa5;`e-3TH@j7kXzBP(p5LxZk_tI^X# zpAg5DBa3olh|`sXv$?HIxwb&8sCGH!HF+eaSTc+Kz}y261zE`cf@e(JNCN{zv08oE zFmd$O?0hXRbKn7mF|{G>t%%W6m@i^^^7*zuJK+s%TGv_M%B`9S59M>PZ??xVwb3W= z?ONcR_Zg6j+U>w^&n(v{cYaL%%*c8i0s$HWS<2`Z`=u6)GpjT!5z33(M!uozMfB6k zl2?+jjC`0vLb4#qAlszK{G@e4^tMf<0C5PNm zgEDDLC;XFA-L!1fbe|#U)np_H#tFA<{PJf(8eVvyDT?7of>9TVHH;ci0N6a`JS zQRt3z6ahyAW)n7S2@Gbj?JZk|v(Bp$C7pF8X~N7$46`8fV2QE3bkTqK@+B~WxuHcJd@HbZ@n@9^sy z_duPF22e%QKN8t-uxb@W?9h&&FN7>X9AjXHMGBQ}u_4x7H`p9-*6VX&Tr&?@wM7WL z*24^>c|@smE42%nr#71Z2gnzHYy|S#Fp=r7zp9>5=_BlZ4@yNiQv$EaBsGz%^se>K zH702cnP5Dx&59NB4c4vxY^YA6pv=l0!)V56LQ2sGxjGzEJF)tw8+-*xFfuB!&nD1H zBkQGnf03LxeD-CX!@Jz$-|==z=@!J_+mXfV1b1Ui>z;~u9B|C(2)1$Fnxt?!BNXsY zR>QoG!}5*>8epoDB1bM0;}o|QjJdb*90eRZ!8eAPJ!?Xy{08@L5_~X`s`PP z*ucoYNHC}N*MP~G|B&GP-HV?Z#ZNY(O}CK$rM))~r}F>%MH?gwWh_EiEJKov8KYRm z5{f8AnTM1@NRrC9B(owzGBlTziVT^SdDf(o%tJ}WBFcW<-)H~MdCs}6XaBMHv-i2q zzV>zX*Y}&;>t6T$9$wRDYuAso)tcc0;`X%5k&$e!jj>MRCEvfRpBw&M`Q@cJqni2O zue#TueA3`D1AlhWrYUgjNi9@0*0ohc025GnRaO)4O+C3KckZzqkP4VsPU4Q@R}ky@ znDawN3^g&pbg6`2;gph9?7U=n|9nFrHbU{)&sb(fsh4-ghAzLFXF?~r)yeW4xxK~vDA}%81a$&1;_cd^?b{s)O>m5oetbHlVBHOH&Cr@Rg^M*$wSJV z_SDT`2el|Z-v4L&@>&NCB5!M4f6CtbNTo(ax2vbtUFG)42g;`ihJR^Wz)btfQtriA$?s$G2niycu2efs(cN!M6|Ea6C@u?WcE~k9ayL< zC6rW5s`XcRsBGugGSw;kX3+`t^VIMJG|w+btd^$)!<~fJ=|%C7p2ml)k-y3PYjSPd zP5qDz+LI?*P7R5uEcUXC17wD(1g#TscAUm^BEQ+#GVrGDTj# zr8{OGz@#c>wYM;wpJzp{U-#nfo>*qZlLn;5p~#GgA;8`jkRHZmZL%ybUHMoC`s2pm zLI$5EiCJMY92GnkVu4#prh6NdpJ)CG7^vft`Z$0rtITj=#Rz6{sOfN!`y!aeP8|#2KXYtwLhBwycZB;-M3UQYIMrD%qY>T&eU!2e{VqQZDZ=tiV{#bEP+-vg-UF{A?^op9f{uQgi^imhH^S zJXaXc{z8;TU!3Ht5`2(qk=!*@ptmAdBD4I(Tq}KJ#a1toXinjOEsD%P7z)P*d0QfF z%qSfl6EHUa<=Bi$UTRz zh%-e>i<|15kr5&@c9G*wopO-2`_NY$IyWAee3NBNW@plt{kM&%eTby`h<6?hj77;E z3``DrW9zf1+(-H}=qm4B8JWAVU9IP^s@%nv+w)sLknGeWVpfyIyvHcFzkf)}Gf~qS zB>WPug}eB#JqR126T95;ZFZGR%EwVu{+?rba)8BdC2ftV2o|l5AMc9NG<;+#=3kU< zPO$I)Q4zfQiMLRz-ll-x|H1;;uQBqm>JyHfAER7%!w##Q>elKB)P1~8*V;P%t1nr) zj_E$)(OyiH3a6=v?esJ1U3#-6obYB~adYySN9p5utp2Gy1UbXp=` zNG|z8qQ=Z&a>yF$;(bT?x+J^vvE{6Pyq4V%un(c^`d$2KMF(NvFSO7rv8pr;|P8JtN>*r}?h4kjX+YCiz>@OTWUZhK(_)vW2 z6m?eoT8;sSXb5Moc&+h&rk^UVfpU);!3`P6IJ6Mpa)9LDh0{`PlL1~2z-KowTrshQ zaz==&(996^fhJ~!mkM@kRCr-cc+h~kVw^jMUFZPa??8QI9qL3WB%^Xiiqf{9yB4G4 zN9cTje~q$`JDBd(7&c&*ghhy$=d$%ZzP`B!rK%bs{H{Is5&aS6>7{)&tC5u9WxlO{ z!Q*Z{QpEe%4Wi)O$RzBEIqq;h;cXFAbgWZ(Z|%^a!^QX#=k~^4jX_d#bFL3Kl z$t`vTABv8O`ri4ZA2e)%|E!yKAZ*;Q!puRKe;)oN%%!tvZLXh-p7$#}A6Y64gy(#< z{|sPDG@}4wnkBt$D@3r{Gu=>DLJ*?q_=tzJ^!t4oY(A-r)F zNM%7AIK+tY9^Yj|;tZts2Hg6!Zb}Oo@^-qo)#qkL?7XEq?=!VFEck~S2<8aTkP%Tf zmx@gv^Uafs#_iB#<=@B4fmv+rn}6tD;p1t-Kf$U=E*Z={{M3=u*Qaq2FT2k{K;%`n+-6*4Q?1)z%$agjtlG6q$#rY`{VDXtQ&ss3i0WuD*KxhVGy&*<@ ztZKup88NGe{l>Wd_BG7E(a)RPlM|-rmju>!KBFGu=aLu$gGSlp^o0{aq#>piPQ5pm zS6PhTr?(i=pY6O)I^~dzrGp5=tl2I4F!B?@>|~6@U!ofU(Is9Vh~qd<_AOaJz*TOrmq7E+#X732fZkeN~w;N7g@`sOZ|n4rIf zIeyohU2guQWlK>m*=ixYZ9M8GG)N52%DvRO*;bN4=ZTiLFYe!opO2Guezw0s1#@s$#HexGZ<>PmXh;mJ$pJ z1GlH~j9tr%w_?U!Y);&iAWk&^)?j>?#CNI8BrBE@akEFso{Gq)1b(4*g~O@%|NI5~ zr3M?~_KW4D(#JfWs-2`%8wB2&?(vI%=P}-;zH}xa~J4ZQ6x=&Qc z3zS(4+DEx=@l9VnW=1aP8z)5@ckUCu^!#7ZQJfK>KlL_yiBawy;FgS8xcb1L*IOYa z;MEqg>?PNm_7}u9iP9=w?OPYbu%hv+;cgl~6W-97mLB4;nFv)uS@hBY9+`m2+)IbZ z{y+)jC|rUxLGy-@FPvBbtO>e+qkjMT@#DPVG4@pp)D9YTvtbEE;z$g;r1TBj=THa5 z9a3(j9{oTN8{@*4*KZN3&OGKiV3FSLe?RkIG|s7yD=a08&Bm+X4(16dknj>A){iPZr%n)C7Q! zKR~i9cX@xurhOEfE;I$zrt*nUtsc08FYl%EmP+Hf2jEP|$htF?c=R=0$58Hfywu2Ouevp?3)_8unQ?4M!14y>Ns5wFUo zTQsY}IrJ|Ev^ZjAAMd_m?kVd#g?(YI;I@bUvtM)B>P^%#l8*Y<(-S$xc5;i;_*Zca zzOP)KAhlDKbAw`hh;fKW_^9b^qYc_qNfyyAM+=}&;W4k8P&n-+#p1m7X%I;XuMPM% zP2q$!n@^z9^B+R=Yr2w)$;kG7Wte1yk{pN8e_WbVLZh4ycuJy=ns*!0Cn78l8JT}7 z6I#t#`?VLMU9>?6VX+4#T_?g@U@mHvnh?&5rJK3xkv7PfW-%QO_9L$c;vibYF!6|n zRm5PGY@+6PVLy5!gN<#P?a+9!>0Ge$+HDTYvh;5YT_u&S-v8xKhwLoLXMSwatOshS z4RFXnL@{}j00h!prJ4~}p~>zca1@PozE1JrcZyE?cFy!cJV7z8ttX}`*|w@}(j`Ev ziaVU!i=fAQqDY@LN|s;#I`|vI-BXO*DH|yl65f`LC0S5)&ARd;a#wJ^AtQXG18uyn zT?Ya)^jyt|RcwtV+nltEJ_=-M-}xRka96}XXhOLj5a*6((Rl)g>VOw2s1bZ*uCi2m z;utrXK>c=o3haxJ$_t&R@6iVKDz#zaW%q#QzNzo(?NnLXavNo&XGmnWVQe~qA!=?v zNS)e|LXfR#ETli{SMElTOi;t_c%hih`pfG6j4V#iYW$!HtO?2DOz}9{mDTJ?3Q6eX z2(!ZK$o;SkV>D9clK-6Ed`<4iS8KzWk@nz{ubwbn4ICN{lJ@)22Rl|zcYO7r4(zm2!^E!#ox6v6)bbBt^`kG!({D5bG=i%NSl2ycS z?`lx;p;o=thN55a{oTwil3*_(oWS#E`Gw!X^0r8*~hAYYNxUZ8ZT3Qp5&d7ffniWL%#U+;)XR zKkGeqouqVpn^i~=+dj`ZXRRBQ3WDz(BJ2ps8mV7$HP@c_rTk&pIPTYft&PMz#3+_k zUSwY58;!&KZ?emk>e8+CrSV{H{7B$aoIQa{l>)3k#$FoNz^=e(0 z;==(#FVx;C-K=>-?G2DwW_JD^V4y>}Gc=CzuLyvAa&nW9vMc9d+iMEs_O&OZ$4Q4w zPqM9-_;qb(5Sfu)kfcwOjy zmFVhU@aMaqVvf*?GYgLjy6vTi=r@;%|(h{itgdWU&olzCO@(*ou~y3=l$FcV+~_1FX@ZGb%WzVwfm z`_SgUnbU@9y(U?{fuGzRmti)4ae=;tv&aOS?WkZ|Rw&HOlEv?haC}_1_S(~G2 z^!1%luC0AaugzoCdhaRH$!O1EKWncapKb`DV08LuzhGxxRnZT!JYL90OXqZrJ9O=q z&=r#Yq-anYyPTv9R(d_GPjMLNU(yuQ^3a$t3}B6Vt#G1KKEumgxY~t2^`#Z7^A~JX zPQoyh_y2uXq(k=Y)62Uy^jTo*yGRH8Y7V}^0R?V_K@UL%lL%ye?Pf&BFU8Qg-byxI zX-ehQJ}T|Ik@=`tV5CLazH0*46&#pXa>of23|!uv>xU(4!?nfDub*zJ^}9YuhA4Bq zuE{klgL)K%Xu7rB>TTjT;&JTuPL`JY;+n zd&)DW|2E+&@jXTQObwi6Y4Xe$mW|pw!s8<|S4z)00K^^mIQZzX@AiCjJs89zx;HZ80QhkeJZ2gtR?+24#B)hPDwnBR(4N9Q=H_Y5j$#Va61_)>#iJ;HZJ$B5cjm8AosIGRXE$GNdy5_kf5{`HaitD8l~>y43tWGh zP?Gd#*tBn^#KqchqPXzbvgRa8ya{V_Qld~8x1?Fn@J0`f9E?8N2);!8J>^aKI*0Bb zESCHy4rE`PYs%)euaO{Ad1;OYBN&@a1IR%~LU}rWW~{Ama(5h(sdW8~_xs>OORk6V zoey^}pUlt)w6cOS_1#@oU`!yWD`IZd3Xze;-eiMl7mXY^`}Y~C*U(zzvrSpnTMkrC4jv3g0ywO~>IQa77Yw~=Pg`@KswmWUaa)i7t#+|ZZGqfzyY~)kO)PBtKH4#)I#7AxP=v9()tulf;p^Gx zUMqV=gAag(OpqizP3TH>*|7BbV?2PkncdX#sbf84={)8j6R&P!7>Yd(3AA{0*azU< zIvI1@f`J86VCtXZMxl*5afu=>uaE{R^~fR39@{XtF=B>TE>him|G~mWKHcTssP;_0 z_g>tiNP{cXhN{;&+t+P98i0lu%^8t}$(QobGfeF3m}znvquex3a5^{E^Kr*n53Nl5 z9bsLMo%Y0xg~zQt2UdA;^;#i_fjBFl-(BuMKs9*tkP=r@zEA8eU8>RGGm%O$#d20&IPK6@;x*jjE^DJtkCpfC<>1QjDf8Y!<2DtaY5huy z6OCYLckSZYsqmdvN#Wj^)n6S?iCfmNi=N21Y{NvO!6){*?7|ZIAD7T*cWE)tn!2O{ zh?-^fJqvWLgOG!ph*t~*c`3ZP1b?tr6%kuf0)BgM@m7Ck^(4i=DJa+c#|p z?!v!q1~-72sGR7G%_#>%{`o^3ksXMvg)Z;c#^%NNhdxXIf_iLrs{!oIM`Q|}#TUTg z@3DB>z0auRj+xlEQIm4szc1BwSvc<18(wx@Pst#~2(=nDji>4FR{q%A)F*T?Do^vXU62)W~@tHA)I06;Zfnd zYrhA%i~T5SUB25}nSo{(ZO}+4Ai)MynS*1 zrXvqe^N3j_q#V7<*mexf7yR@@1VPgEY4Vz+sm1AfS;8_YS_HkCj(R=G?nKr{@YB`9 zhhGJ}+{Dnd0(dhl4>kh()8|KNmBW%$fgN86u?a)s*UuN6mCpZZhr5Uo_NKD<|2f*0 z|H;W!IcMa!A-^V~n8Cu>J2(L1(Z*eaK2JlJz9%*3FbyF%u5C9j@sNSTj{yQgrYn>! zs`d;zY%EaoXku|TnUOwHGy^M499fSU7_7emboI!b4ih{{q+n64j^H5!0pgDa#0QQZ z5IR7k4E58J9qGVhg=s!r;OWu;J|ZaZ330H1$F>TudxxKYg>4_n*v9q$_UIh}Y+vi} z84chL0)vuGv_)RSOoBI32DG=V5YxnA~nM7MQNI*7e>k!#WJCP9TGeZwkE1P#3C0)IK@13+(FThAN#5LVRMG$yu3- zBm~ne{O)W+yA*XCi?Zvg?|9efkwWiLvlF-41Ye+!V=p66VMJR!F8kZ?R9#Hi2A><@ z69ot(wuM{)a3r?k{I3`{8LBfMS}_|R&M}AsxdX&%fN+u)`6vxSL+}Wa7Wm#{8zIX1 zHPSAQ3G<(;u?^K_5y|4|-u9F0zW3ssCqu$}@X7|IsiarmsVgLD7|x`OlJk&mp$#f@ zy~;7j#ll;VkBIy$-*>1kPweI0P(@xKulPOz;96RBv%_Xmd}Y6nmZTMIOowbaMq0oQ zgIH?T|A{YIc&)%gt*q%xoO(bG;V^A1gf=3&LMJT+FQA9kPU&nzfI7InPY;C#uRrmi zQLgyP(enwb3pn4$1V)Qcr7pnXgMiJa2)Mx#60Y?s*p>(bT+JHJ#>e5gGdb^b-ucd8 zt0*BOHgIvW7ErIsu@1(U zEYF>4a|;kjF0f>f40?lLB@65Nf|G-ccDMl7ulSjOSo8SvLM$sU@8Fj^ihcuMeck6& z)rtllPwn1}9L^+JQ@X@Ue_m(5RFgQr;DDWubPq&83!n^#Oa zXk`}`78);Id>+a(;P+;E>L9=xDFIj;q`m0bd_Q-nCE) zh|e^F&*T_^0F2@UCi)#~grN6+i?hRuVqgZR#%PtF5&bZ#I?3S2r-C0qj61pbjS&V? zaocw3ZU>}kdB-hxnJ3G0n%F?U4f{+(Jd{I@nI_s zbixBn86;8OKK0{&OaHIR)DvX5oo$&d0IG2mWi`PU*o_#hn*_$#kmEeW~KS6Y^%;v5t^))nR~y zy|*5sJ4&$jx+l7VD7ef0Hz`4S3eo#e6XbV+l0?Rutwxma_7r}e#_^M*mjLb}cKe4* zQ*s>o4N$N}jAsy*l0~bZ-g{l-bKL(2Hi6MyP=mRk-aN2)FeFS*cW7@)QHgBEyX(d} zS$&yzlOnQ?n2@|m)O1`%JQVNl?RUX2j#i`zgA|eko;o*w zEhYbyWSxSrbZ*2nFLI6N4&@CGKP5<|MwRFK#Sr z#I=HdLi@0BZ}>f%GVQ84yrA>+r)}v1+fIypv|))-;2b$+$%AUI?LQ-3rI}sY|3pz< z0y@!<(jGr+-G&bVAeWs_kFhCzgyHea$*qvekDxxPpT4H#Sl5Oh3F- zK7Vu=1M|3_h*44vWu$&clfGKK3jYdlTIw-5M+s@=NytAR`GI?=MW5-ltAl-Ttvf-<}J1+Uug8a9Gi;G_QM zfF{nBax~;h;3S_3)LI_Boo}wp_w4_$@#jk2+m8Y7dv6Q5QpjH;@ahH+bMbZ}f)yw< z$t8SwYjbEUr|Kn6{%c3dS^x40_8kp9Ynr*2Mvv6xrPRO8rdNJCS<@_N|IAMWF^WS@ zBGSkoSW90T@-|?e{<0kzFF|dj(nJrHRG!y9`)&DIlKD+M{)w>Fab6_?Iiqlv;^p$I zudpJFU+4MwOTE-qbD*Pbt$mH35krI*lM7Dm_%TCT;GEI@J=M8nO zc~MY8l(;kXD{el3*ZUE86L@RLo5HNxkx5%b0I2=@6QZfBw{7S7QO{)DnO0{40)Mn> zW_Q5)l5Jy=ocdY;bMnqBF4-v>Gdw+gb2VG;2jP^Y##? zZOklxLWC7P`>0E9{D&yP2K*RLudf|-dk1W5-(_rblYAC9u}kgRE_xN0GUL|(BpJFqE1&04xAv3hrHAil9Rg+?&2=#lB3wZ5T824(3ma%HUxbl z1k9pO<9n)^ix2kNmp^9s{4z~PoQtZ)T^DqvDC4U-vHJB3V{{aXAP6&di~b= zU^?-I=^R7RI5KKXX7^RqX*2bK}xq}U1`5%?Er?6zr5Pjzb+3E<^6l4@f^GFaGD{_Ul%Nsd_)NtNU zcWkOgT+ulkR6-&LHLxD&z#jAk7YByY0}~IId#aio!Z)USa*i3?J~~9Mc<6Rm4Ecw^ z?CB@zJ1LuiDv>M$KS2-WS-TOz{?gWk6EM7h8|0I^mWc5gQ9!HDAbyM&;e$Can_^VjeCR2~ zp#@1EX>Vwd{X=mSxHN^l2}C3HLR}DX94nn9VcA>rSu^%)9VT+9mP|#a<94DmsY7e{ za;jnTljUHLl%dka!=Q0b=(jB)EyE#39$Sv!Avop!`gSv-0yo&Nd>4^Rxi$0Y6Q=$i zv16~Wv?*@QLB8#GgrV5+>R!ccJ)Gl(+0h?*RG{mg;9>~M?~qlgzmJ(9^jpG;js`Lo z67dz-K^FjdlLCG}^qO_W*aaDpvI^|*jkXFbjTP@U+q_x?;)3%d(xmnZE3>?gSzUl5 zvLL(Y*z>XmIELoEG|+lQ06L!4K!Wg2rz8(lF?0jRfQ-y3eE}m-r zw5%sN$g=23ajuKC!`W2he2yap0XeZ2<{oD1Abs^?TnqpDGZ9w+r?ufK^G;g2t%Ujw z>Xp4OALz5+nXwR-=!=XRK0VwVRJ#E*kI%Y-Y4gMB{hr+LLQk|WN0!&{XbfZeA1!CakXL-3sy z1?9Bowv!6E_99%{SPz6-sQ4bWwP6Y{H^!en`-fKGz6}f?js*eiX~@wLcyk zCOfe<>)*&OPV*<5hx8-P{lqoT{dud!KMEzSsqo4&WTkQ2@t*Lr-|XsEeu}}V=D043 zfBI5;JuO|nhm(uVRm6_jo@%R>XB3;t&qO9k8QadVB-nu*VS3pvuA2!3bIqLb+e499 zVdTpTMVg5*+1@Q_dE0%E_Chm{dj$E2Zm;|F z&0Vf=)0O;FP#2=uyKQwtt4@NDWcyF%}hlxx$mohNNlOI(9CqhD4zU~ipr;V*t ziDyUS`G?*L-EhFm?zy7lAq7XetUT=rO{c8tv5p*=>rB3ed+_T9t2nZMMBoTlHlI!U zAbd6`^ex4TX1mH%9(Uh5Dp$7X^|$Z6?PPBw=Vs-faS6XGDyvD=8WSy%2td6YyCP}S zFN`SsqL%k&oYv-6Pu4wnGMhJu$<_FkAN=R@rFcs19K zh{r2J@2z!m`UY!FJ67NbT~P}Yw<^X$Wx(?W^V9t~^@gm6XiPsh?xi*_J+e8~^ee(> z(7XZlZhVS!f=%L}wdbmL+CSyB(-Smk(=i77hh7Rg(JYrh8$b|zObu9_XO^uUux zt;n0IoL|p3zdT9@orvnq(%#~7eiqir!uA=R=h3Q?q?EaaSt~|An^T_n?p8Y;s@<_Z zN2ZB>xis(mg75TaX{oFIf24!s{6!?5P`K%qg%wBbovnKxYx3+FX%I8R`I+@5K=GR$ zX%Lr_6Fv9%nk_7R(9NMV6#hTLuEqXe(<9ySEJ+kHWI_#l0cYD?6nV`)E;uYl3OwHa z!m46^X8iM~bZNVVe<;7k1HWC(#nPaR>+h<=%{o*U_{Z)InXn)PLt-CF(} z#$5u3xBwPZDf_6IL6m9^m@{DfRAb`IpRsf9cJWx@LDL`FOuHAFcr6E{E|J;|>==O1 z96%cYX6GBX6c7p^5cnzE+y%}DTb%yAqg!{`GNyJKb*K4o3mhA_`mc{hHGq*=gaD8j zr(~RMPT_?C1PVfeNb8gwDgF(@ps?>ULMuE<$5n^e*2{JR2!SWd%d~r)+23!H1|8r^ z)W4(Wy({h-9>R#Q00A%Zt=(`#QBK3zH<+R9B1A>5Vsr7nR5 zRrXuNl#$tesv{?&|Gfo{HHt9-tB~Y$C2U~9#Kwyje|8&lZUe9VhxIb%u7J?27x9EN zJ#I@jO<67@Cl@W*5;U(ZwelchHV?lz5%Hi^J~B)5onB1eSF|BU?C~MWfZ^HLWPSv> zd0k))stB)fzb!|yB!{Y>|E(O3VbQ0^d&!;Suj&${m;I-Z96fq##%_9gUCme0PO*~) zhC!^g42Haz*RC1bw5A4K^5EAOX8V>$!ryY>tzpHH(8nmCG=(=Am;Q0f7_;ez#-Rv!NX;=FvQJ&LcxE-HWCjR4TYP(IRv#(sMDzEg5jQG~FvuZ`O>d|GUB&98?E8k#+ATrV%gclALO1Eij zSP9vNn}AJuYAem`QkgVY`aCq?q!x}0x?p_#O@S{sg7V2A=_db|?-0o=oA}_oY-)F-KXyKJ#^*uN20kuDhL<$k1i(4dwCrL$2s}f<4O6B^5ir6 zCQGS%_cY@~k_+lC{Z3Mat;ExLt9-j%Csvxs`MMP%o4mCi@w!aIR5o>kL#N_h-L1I0 zX9wc+^WtUC-tG+!4*UG8NvV}{QL(J{`)iArzPXXB8_olhUnN(Ppym3CLJp|lr?l7y zQa0)lx*#8_Vdk&+kcm~N?}1QH4pF;<=6RaRilcbotND#!-2WPq`#*@_%;_v!wrpuB kx { expect(saved.setup.webhookVerifiedAt).toBeNull(); expect(saved.providerAccountId).toBe("TAUTO"); expect(saved.setup.slackAccount).toEqual({ externalUserId: f.installerId, paperclipUserId: actor.userId, status: "linked", welcomeStatus: "sent", dmChannelId: "DWELCOME" }); - expect(await f.service.listPrincipals(f.endpoint.id)).toEqual([expect.objectContaining({ status: "linked", paperclipUserId: actor.userId })]); + expect(await f.service.listPrincipals(f.endpoint.id)).toEqual([expect.objectContaining({ status: "linked", paperclipUserId: actor.userId, paperclipUserImage: null })]); + await db.update(authUsers).set({ image: "https://example.test/installer.png" }).where(eq(authUsers.id, actor.userId)); + expect(await f.service.listPrincipals(f.endpoint.id)).toEqual([expect.objectContaining({ paperclipUserImage: "https://example.test/installer.png" })]); + await db.update(authUsers).set({ image: null }).where(eq(authUsers.id, actor.userId)); const [, welcome] = f.provider.mock.calls.find(([url]) => String(url).endsWith("chat.postMessage"))!; expect(new URLSearchParams(String(welcome!.body)).get("channel")).toBe(f.installerId); expect(new URLSearchParams(String(welcome!.body)).get("text")).toMatch(/^Hi, I’m .+\. Your Slack account is connected to Paperclip\.$/); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 6efc1b198cd..25b6c7278f0 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -910,6 +910,7 @@ const chatPrincipalLinkResponseSchema = z externalDetail: z.string(), paperclipUserId: z.string().nullable(), paperclipUserLabel: z.string().nullable(), + paperclipUserImage: z.string().nullable(), status: chatIdentityLinkStatusSchema, }) .strict(); diff --git a/server/src/services/chat-channels/identity-links.ts b/server/src/services/chat-channels/identity-links.ts index 47596405f3a..b45ee22db66 100644 --- a/server/src/services/chat-channels/identity-links.ts +++ b/server/src/services/chat-channels/identity-links.ts @@ -56,6 +56,7 @@ export function createChatIdentityLinks(db: Db, options: ChatIdentityLinkOptions id: authUsers.id, name: authUsers.name, email: authUsers.email, + image: authUsers.image, }) .from(authUsers) .where(inArray(authUsers.id, userIds)) @@ -80,6 +81,7 @@ export function createChatIdentityLinks(db: Db, options: ChatIdentityLinkOptions : principal.externalId, paperclipUserId: link?.paperclipUserId ?? null, paperclipUserLabel: user?.name ?? user?.email ?? null, + paperclipUserImage: user?.image ?? null, status: link?.status ?? "pending", lastConnectAt: connectByPrincipal.get(principal.id) ?? null, }; diff --git a/ui/src/api/chatEndpoints.ts b/ui/src/api/chatEndpoints.ts index 79e0c43b15f..d50a6756c3b 100644 --- a/ui/src/api/chatEndpoints.ts +++ b/ui/src/api/chatEndpoints.ts @@ -59,6 +59,7 @@ export interface ChatIdentityLink { externalDetail?: string | null; paperclipUserId?: string | null; paperclipUserLabel?: string | null; + paperclipUserImage?: string | null; status: "linked" | "pending" | "revoked"; } diff --git a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx index 3544343a539..2c07fc4d894 100644 --- a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx +++ b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx @@ -1,8 +1,9 @@ +import { deriveInitials } from "@/components/Identity"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { agentAvatarUrl } from "@/lib/agent-avatar-url"; import { resolveAgentAppearance } from "@paperclipai/shared"; import { SlackTestMessageButton } from "./SlackTestMessageButton"; import { AgentAvatar } from "@/components/AgentAvatar"; -import { Identity } from "@/components/Identity"; import { SlackSetupAdvanced } from "./SlackAppDetails"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { SlackToolsSettings, SlackSearchAccess } from "./SlackToolSettings"; @@ -782,13 +783,19 @@ function Access({ role="listitem" className="flex flex-wrap items-center gap-3 py-3" > -
- - {(link.status !== "linked" || link.externalLabel !== link.paperclipUserLabel) &&

- {link.status === "revoked" ? "Disconnected" : link.status === "linked" - ? link.externalLabel - : "Waiting for account confirmation"} -

} +
+ +
+

{link.paperclipUserLabel ?? link.externalLabel}

+ {(link.status !== "linked" || link.externalLabel !== link.paperclipUserLabel) &&

+ {link.status === "revoked" ? "Disconnected" : link.status === "linked" + ? link.externalLabel + : "Waiting for account confirmation"} +

} +
{link.status === "linked" ? (